Initial commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[env]
|
||||
_.file = '.env'
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# AGENTS.md — pi-perplexity
|
||||
|
||||
## What This Is
|
||||
|
||||
A **pi extension** (plugin for `@mariozechner/pi-coding-agent`) that provides web search via a Perplexity Pro/Max subscription. Uses OAuth JWT authentication against Perplexity's internal SSE endpoint — no API credits consumed, only the subscription.
|
||||
|
||||
## Build / Test / Lint
|
||||
|
||||
```bash
|
||||
# Type check
|
||||
bunx tsc --noEmit
|
||||
|
||||
# Run tests
|
||||
bun test
|
||||
|
||||
# Quick smoke test — factory returns valid tool shape
|
||||
bun run --bun src/index.ts
|
||||
```
|
||||
|
||||
No build step. Extensions are loaded via jiti — TypeScript runs directly.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
pi-perplexity/
|
||||
package.json # pi extension manifest (see "omp"/"pi" field)
|
||||
tsconfig.json
|
||||
AGENTS.md # You are here
|
||||
architecture.md # Full protocol spec — SSE format, auth flows, event schemas
|
||||
plan.md # Implementation phases and acceptance criteria
|
||||
docs/
|
||||
pi_docs_extension.md # Official pi extension system documentation
|
||||
pi_platform_reference.md # Pi platform reference (SDK, RPC, sessions, settings, packages)
|
||||
src/
|
||||
index.ts # CustomToolFactory entry — default export
|
||||
auth/
|
||||
jwt.ts # JWT base64url decode, expiry extraction
|
||||
login.ts # macOS app extraction + email OTP flow
|
||||
storage.ts # Token persistence (~/.config/pi-perplexity/auth.json)
|
||||
search/
|
||||
types.ts # All type definitions (StreamEvent, SearchResult, etc.)
|
||||
client.ts # HTTP POST to SSE endpoint, orchestrates stream + merge
|
||||
stream.ts # SSE line parser + incremental event merging
|
||||
format.ts # SearchResult → LLM-readable text output
|
||||
render/
|
||||
call.ts # TUI renderCall component
|
||||
result.ts # TUI renderResult component
|
||||
```
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
### Zero Runtime Dependencies
|
||||
This plugin has **zero npm dependencies**. All HTTP, SSE parsing, JWT decoding, and UUID generation use platform globals:
|
||||
- `fetch` — global (Bun/Node 18+)
|
||||
- `crypto.randomUUID()` — global
|
||||
- `atob` / `Buffer.from(payload, "base64url")` — global
|
||||
- `Intl.DateTimeFormat` — global
|
||||
|
||||
Do NOT add dependencies to `dependencies` in package.json.
|
||||
|
||||
### Peer Dependencies Only
|
||||
These are bundled by pi and must go in `peerDependencies` with `"*"` range:
|
||||
- `@sinclair/typebox` — schema definitions (injected at runtime via `api.typebox`)
|
||||
- `@mariozechner/pi-tui` — TUI Component types for renderers
|
||||
|
||||
### Reverse-Engineered API
|
||||
The Perplexity SSE endpoint is **not a public API**. It can break without notice.
|
||||
- Keep types loose — all fields optional
|
||||
- Keep the client thin — minimal assumptions about response shape
|
||||
- Specific User-Agent and headers are required (see architecture.md § Request)
|
||||
- `is_incognito: true` always — don't pollute user's Perplexity history
|
||||
|
||||
## Coding Conventions
|
||||
|
||||
### TypeScript
|
||||
- `strict: true`, `noEmit: true`
|
||||
- Target: ESNext, module: ESNext, moduleResolution: bundler
|
||||
- Use `interface` for data shapes, `type` for unions/aliases
|
||||
- All stream event fields are optional — the API is unstable
|
||||
|
||||
### Extension API
|
||||
- Entry point: `src/index.ts` exports `default` function or factory
|
||||
- Import types from `@mariozechner/pi-coding-agent`
|
||||
- Use `StringEnum` from `@mariozechner/pi-ai` for string enum params — `Type.Union`/`Type.Literal` breaks Google's API
|
||||
- Tool execute signature: `execute(toolCallId, params, signal, onUpdate, ctx)`
|
||||
- Return shape: `{ content: [{ type: "text", text }], details: { ... } }`
|
||||
|
||||
### Naming
|
||||
- Tool name: `perplexity_search` (snake_case, matches pi convention)
|
||||
- File names: lowercase, descriptive (`stream.ts`, `client.ts`, `jwt.ts`)
|
||||
- Types: PascalCase (`StreamEvent`, `SearchResult`, `StoredToken`)
|
||||
- Constants: UPPER_SNAKE or camelCase for compound values
|
||||
|
||||
### Error Handling
|
||||
- Never throw raw errors from tool execute — always return error text in `content`
|
||||
- Use a `SearchError` class for typed errors with code and message
|
||||
- HTTP 401/403: clear stored token, return auth error to agent
|
||||
- HTTP 429: return rate limit message with retry suggestion
|
||||
- Network failures: return error text, don't crash
|
||||
- Empty responses: return "No results found"
|
||||
- JWT errors: fallback expiry of 1 hour if decode fails
|
||||
|
||||
## Auth Flow (two paths, tried in order)
|
||||
|
||||
1. **macOS Desktop App** (zero-interaction): `defaults read ai.perplexity.mac authToken`
|
||||
- Skip if `PI_AUTH_NO_BORROW=1` is set
|
||||
- Returns null on non-macOS or if app not installed
|
||||
2. **Email OTP** (interactive fallback): CSRF → send OTP → verify OTP
|
||||
- Uses `ctx.ui.input()` for OTP prompt
|
||||
|
||||
Token stored at `~/.config/pi-perplexity/auth.json` with `0600` permissions.
|
||||
JWT expiry: `decoded.exp * 1000 - 5min` buffer. No auto-refresh — re-login on expiry.
|
||||
|
||||
## SSE Stream Protocol
|
||||
|
||||
Endpoint: `POST https://www.perplexity.ai/rest/sse/perplexity_ask`
|
||||
|
||||
Events are **incremental snapshots** that must be merged:
|
||||
- Top-level fields: shallow merge
|
||||
- Blocks: keyed by `intended_usage` — merge, don't replace array
|
||||
- Markdown chunks: respect `chunk_starting_offset` for splice
|
||||
- Sources: accumulate, never replace; preserve from earlier events
|
||||
|
||||
Stream terminates when `event.final === true` or `event.status === "COMPLETED"`.
|
||||
|
||||
Answer extraction priority: markdown blocks → ask_text blocks → event.text fallback.
|
||||
Source extraction priority: web_results block → sources_list fallback. Deduplicate by URL.
|
||||
|
||||
Full protocol details: `architecture.md` § Search Protocol, § Response: SSE Event Stream.
|
||||
|
||||
## Tool Output Format
|
||||
|
||||
```
|
||||
## Answer
|
||||
<synthesized answer>
|
||||
|
||||
## Sources
|
||||
N sources
|
||||
[1] Title (2d ago)
|
||||
https://url
|
||||
snippet preview...
|
||||
|
||||
## Meta
|
||||
Provider: perplexity (oauth)
|
||||
Model: <display_model>
|
||||
```
|
||||
|
||||
- Age: human-readable relative time ("2d ago", "3h ago", "just now")
|
||||
- Snippets: truncated to 240 chars
|
||||
- Source count: respect `limit` parameter
|
||||
|
||||
## Key References
|
||||
|
||||
| What | Where |
|
||||
|------|-------|
|
||||
| Full protocol spec (headers, body, SSE events) | `architecture.md` |
|
||||
| Implementation phases and acceptance criteria | `plan.md` |
|
||||
| Pi extension system overview | `docs/pi_docs_extension.md` |
|
||||
| Pi platform reference (SDK, RPC, sessions, settings, packages) | `docs/pi_platform_reference.md` |
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
- `Type.Union([Type.Literal("a"), ...])` does NOT work for Google models — use `StringEnum` from `@mariozechner/pi-ai`
|
||||
- Tool `execute` param order is `(toolCallId, params, signal, onUpdate, ctx)` — signal before onUpdate
|
||||
- The SSE stream is NOT standard Server-Sent Events — it uses `data:` lines with JSON but requires custom parsing for multi-line data fields and `[DONE]` marker
|
||||
- Perplexity SSE events are incremental snapshots, not deltas — each event contains the full state up to that point, but blocks must still be merged by `intended_usage` key
|
||||
- macOS `defaults read` via Bun shell (`Bun.$`) — handle non-zero exit code (app not installed) gracefully
|
||||
- JWT `exp` claim is in seconds, not milliseconds — multiply by 1000
|
||||
- Token file must be written with `0600` permissions — use `Bun.write()` and set mode
|
||||
- Always pass `AbortSignal` through to `fetch` for cancellation support
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
# pi-perplexity Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
An oh-my-pi plugin that provides web search via a Perplexity Pro/Max subscription. Uses OAuth JWT authentication against Perplexity's internal SSE endpoint — no API credits consumed, only the subscription.
|
||||
|
||||
## System Context
|
||||
|
||||
```
|
||||
oh-my-pi (coding-agent)
|
||||
|
|
||||
+-- plugin loader (discovers pi-perplexity via package.json "omp" manifest)
|
||||
|
|
||||
+-- pi-perplexity (CustomToolFactory)
|
||||
|
|
||||
+-- perplexity_search tool (CustomTool)
|
||||
| |
|
||||
| +-- Auth: JWT from macOS app or email OTP
|
||||
| +-- Search: POST SSE to www.perplexity.ai
|
||||
| +-- Parse: incremental event merging
|
||||
| +-- Render: TUI components for call/result
|
||||
|
|
||||
+-- Token storage (SQLite via omp's AgentStorage, or standalone file)
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### JWT Acquisition (two paths, tried in order)
|
||||
|
||||
**Path 1 — macOS Desktop App Extraction (zero-interaction)**
|
||||
|
||||
The Perplexity macOS Catalyst app (`ai.perplexity.mac`) stores its auth JWT in NSUserDefaults, readable by any same-UID process:
|
||||
|
||||
```bash
|
||||
defaults read ai.perplexity.mac authToken
|
||||
```
|
||||
|
||||
If the app is installed and logged in, this returns the JWT immediately. No browser, no user interaction.
|
||||
|
||||
Skip this path if `PI_AUTH_NO_BORROW=1` is set.
|
||||
|
||||
**Path 2 — Email OTP (interactive, fallback)**
|
||||
|
||||
```
|
||||
GET https://www.perplexity.ai/api/auth/csrf
|
||||
-> { csrfToken: string }
|
||||
|
||||
POST https://www.perplexity.ai/api/auth/signin-email
|
||||
body: { email, csrfToken }
|
||||
-> Sends OTP code to email
|
||||
|
||||
(user enters OTP)
|
||||
|
||||
POST https://www.perplexity.ai/api/auth/signin-otp
|
||||
body: { email, otp, csrfToken }
|
||||
-> { token: "<JWT>" }
|
||||
```
|
||||
|
||||
All auth requests use these headers:
|
||||
```
|
||||
User-Agent: Perplexity/641 CFNetwork/1568 Darwin/25.2.0
|
||||
X-App-ApiVersion: 2.18
|
||||
```
|
||||
|
||||
### JWT Handling
|
||||
|
||||
- Expiry extracted from JWT payload `exp` claim: `decoded.exp * 1000 - 5min`
|
||||
- Fallback expiry: 1 hour from acquisition if decode fails
|
||||
- No automated refresh — Perplexity JWTs are long-lived; re-login on expiry
|
||||
- Storage: persisted to disk so login survives restarts
|
||||
|
||||
### Token Storage
|
||||
|
||||
The JWT is stored as:
|
||||
```typescript
|
||||
{
|
||||
type: "oauth",
|
||||
access: "<JWT>",
|
||||
expires: <exp_ms_minus_5min>,
|
||||
email?: "<user@example.com>"
|
||||
}
|
||||
```
|
||||
|
||||
Two storage strategies (choose during implementation):
|
||||
|
||||
1. **Integrate with omp's AgentStorage** — query `listAuthCredentials("perplexity")` from the agent.db SQLite. Requires access to the db path via `getAgentDbPath()`.
|
||||
2. **Standalone JSON file** — `~/.config/pi-perplexity/auth.json`. Simpler, no dependency on omp internals, portable.
|
||||
|
||||
On search, check stored JWT expiry (with 5-minute buffer). If expired, prompt re-login.
|
||||
|
||||
## Search Protocol
|
||||
|
||||
### Endpoint
|
||||
|
||||
```
|
||||
POST https://www.perplexity.ai/rest/sse/perplexity_ask
|
||||
```
|
||||
|
||||
### Request
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <JWT>
|
||||
Content-Type: application/json
|
||||
Accept: text/event-stream
|
||||
Origin: https://www.perplexity.ai
|
||||
Referer: https://www.perplexity.ai/
|
||||
User-Agent: Perplexity/641 CFNetwork/1568 Darwin/25.2.0
|
||||
X-App-ApiClient: default
|
||||
X-App-ApiVersion: 2.18
|
||||
X-Perplexity-Request-Reason: submit
|
||||
X-Request-ID: <random-uuid>
|
||||
```
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"query_str": "<effective_query>",
|
||||
"params": {
|
||||
"query_str": "<effective_query>",
|
||||
"search_focus": "internet",
|
||||
"mode": "copilot",
|
||||
"model_preference": "pplx_pro_upgraded",
|
||||
"sources": ["web"],
|
||||
"attachments": [],
|
||||
"frontend_uuid": "<random-uuid>",
|
||||
"frontend_context_uuid": "<random-uuid>",
|
||||
"version": "2.18",
|
||||
"language": "en-US",
|
||||
"timezone": "<Intl.DateTimeFormat().resolvedOptions().timeZone>",
|
||||
"search_recency_filter": null | "hour" | "day" | "week" | "month" | "year",
|
||||
"is_incognito": true,
|
||||
"use_schematized_api": true,
|
||||
"skip_search_enabled": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key parameters:
|
||||
- `model_preference: "pplx_pro_upgraded"` — Pro subscription model
|
||||
- `mode: "copilot"` — multi-step reasoning (Pro feature)
|
||||
- `is_incognito: true` — does not save to Perplexity history
|
||||
- `effective_query` — system prompt prepended to user query with `\n\n` separator (no separate system message in this API)
|
||||
|
||||
### Response: SSE Event Stream
|
||||
|
||||
Each `data:` line contains a JSON event. Events are **incremental snapshots** that must be merged.
|
||||
|
||||
#### Event Shape
|
||||
|
||||
```typescript
|
||||
interface StreamEvent {
|
||||
status?: string; // "COMPLETED" on final
|
||||
final?: boolean; // true on last event
|
||||
text?: string; // plain text answer (fallback)
|
||||
blocks?: StreamBlock[]; // structured content blocks
|
||||
sources_list?: StreamSource[];// source references (alternative format)
|
||||
display_model?: string; // model name used
|
||||
uuid?: string; // request ID
|
||||
error_code?: string;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
interface StreamBlock {
|
||||
intended_usage?: string; // "markdown_block" | "ask_text" | "web_results"
|
||||
markdown_block?: {
|
||||
answer?: string;
|
||||
chunks?: string[]; // incremental text chunks
|
||||
chunk_starting_offset?: number;
|
||||
};
|
||||
web_result_block?: {
|
||||
web_results?: WebResult[];
|
||||
};
|
||||
}
|
||||
|
||||
interface WebResult {
|
||||
name?: string;
|
||||
url?: string;
|
||||
snippet?: string;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
interface StreamSource {
|
||||
title?: string;
|
||||
url?: string;
|
||||
snippet?: string;
|
||||
date?: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### Event Merging Strategy
|
||||
|
||||
Events are incremental — each new event is merged into a running snapshot:
|
||||
|
||||
1. **Top-level fields**: shallow merge (`{ ...existing, ...incoming }`)
|
||||
2. **Blocks**: keyed by `intended_usage` — new blocks with same key replace/merge with existing
|
||||
3. **Markdown chunks**: if `chunk_starting_offset` is 0, replace all chunks; otherwise splice at offset
|
||||
4. **Sources**: accumulated, not replaced; `sources_list` preserved from earlier events if absent in later ones
|
||||
|
||||
Stream terminates when `event.final === true` or `event.status === "COMPLETED"`.
|
||||
|
||||
#### Answer Extraction (priority order)
|
||||
|
||||
1. `blocks` where `intended_usage` contains `"markdown"` -> join `chunks[]` or use `answer` field
|
||||
2. `blocks` where `intended_usage === "ask_text"` -> same logic
|
||||
3. `event.text` fallback
|
||||
|
||||
#### Source Extraction (priority order)
|
||||
|
||||
1. `blocks` where `intended_usage === "web_results"` -> `web_result_block.web_results[]`
|
||||
2. `event.sources_list[]` fallback
|
||||
3. Deduplicate by URL
|
||||
|
||||
## Plugin Interface
|
||||
|
||||
### oh-my-pi CustomTool Contract
|
||||
|
||||
The plugin exports a `CustomToolFactory`:
|
||||
|
||||
```typescript
|
||||
type CustomToolFactory = (api: CustomToolAPI) =>
|
||||
CustomTool | CustomTool[] | Promise<CustomTool | CustomTool[]>;
|
||||
```
|
||||
|
||||
Each `CustomTool` implements:
|
||||
```typescript
|
||||
interface CustomTool<TParams, TDetails> {
|
||||
name: string; // "perplexity_search"
|
||||
label: string; // "Perplexity Search"
|
||||
description: string; // tool description for LLM
|
||||
parameters: TSchema; // TypeBox schema
|
||||
execute(toolCallId, params, onUpdate, ctx, signal): Promise<AgentToolResult>;
|
||||
renderCall?(args, theme): Component; // TUI call display
|
||||
renderResult?(result, options, theme): Component; // TUI result display
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Parameters
|
||||
|
||||
```typescript
|
||||
{
|
||||
query: string; // required
|
||||
recency?: "hour" | "day" | "week" | "month" | "year";
|
||||
limit?: number; // max sources to return
|
||||
}
|
||||
```
|
||||
|
||||
### Tool Output
|
||||
|
||||
Text block formatted for LLM consumption:
|
||||
```
|
||||
## Answer
|
||||
<synthesized answer with inline citations>
|
||||
|
||||
## Sources
|
||||
N sources
|
||||
[1] Title (age)
|
||||
https://url
|
||||
snippet...
|
||||
|
||||
## Meta
|
||||
Provider: perplexity (oauth)
|
||||
Model: <display_model>
|
||||
```
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
pi-perplexity/
|
||||
package.json # omp plugin manifest
|
||||
tsconfig.json
|
||||
src/
|
||||
index.ts # CustomToolFactory entry point
|
||||
auth/
|
||||
jwt.ts # JWT decode, expiry extraction
|
||||
login.ts # macOS app extraction + email OTP flow
|
||||
storage.ts # Token persistence (read/write/check expiry)
|
||||
search/
|
||||
client.ts # HTTP request to SSE endpoint
|
||||
stream.ts # SSE parsing + event merging
|
||||
types.ts # All type definitions
|
||||
format.ts # Response formatting for LLM output
|
||||
render/
|
||||
call.ts # TUI renderCall component
|
||||
result.ts # TUI renderResult component
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
### Required
|
||||
- `@sinclair/typebox` — injected by omp via `CustomToolAPI.typebox`, no direct dependency needed
|
||||
- `@oh-my-pi/pi-tui` — for TUI `Component` type in renderers (peer dependency)
|
||||
|
||||
### None (zero runtime dependencies)
|
||||
- `fetch` — global (Bun/Node 18+)
|
||||
- `crypto.randomUUID()` — global
|
||||
- `atob` / `Buffer` — global
|
||||
- `Bun.$` — for `defaults read` on macOS
|
||||
- `Intl.DateTimeFormat` — global
|
||||
|
||||
The plugin should have **zero npm dependencies**. All HTTP, SSE parsing, and JWT decoding use platform APIs.
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Error | Behavior |
|
||||
|---|---|
|
||||
| No JWT found (not logged in) | Return error text to agent: "Not authenticated. Run `omp login perplexity`." |
|
||||
| JWT expired | Attempt re-login via macOS app extraction; if fails, return error prompting manual login |
|
||||
| HTTP 401/403 | JWT revoked or expired; clear stored token, return auth error |
|
||||
| HTTP 429 | Rate limited; return error with retry suggestion |
|
||||
| SSE `error_code` in stream | Extract `error_message`, throw SearchError |
|
||||
| Network failure | Return error text to agent |
|
||||
| Empty response (no answer, no sources) | Return "No results found" |
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- JWT stored on disk with user-only permissions (0600)
|
||||
- `is_incognito: true` prevents queries from appearing in Perplexity history
|
||||
- No API key needed — subscription auth only
|
||||
- Token never logged; only expiry metadata logged at debug level
|
||||
@@ -0,0 +1,585 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "pi-perplexity",
|
||||
"devDependencies": {
|
||||
"@mariozechner/pi-ai": "*",
|
||||
"@mariozechner/pi-coding-agent": "*",
|
||||
"@mariozechner/pi-tui": "*",
|
||||
"@sinclair/typebox": "*",
|
||||
"bun-types": "*",
|
||||
"typescript": "*",
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mariozechner/pi-ai": "*",
|
||||
"@mariozechner/pi-tui": "*",
|
||||
"@sinclair/typebox": "*",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.73.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw=="],
|
||||
|
||||
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||
|
||||
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
||||
|
||||
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
|
||||
|
||||
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
|
||||
|
||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||
|
||||
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.990.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.10", "@aws-sdk/credential-provider-node": "^3.972.9", "@aws-sdk/eventstream-handler-node": "^3.972.5", "@aws-sdk/middleware-eventstream": "^3.972.3", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/middleware-websocket": "^3.972.6", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/token-providers": "3.990.0", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/eventstream-serde-config-resolver": "^4.3.8", "@smithy/eventstream-serde-node": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.30", "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-8TtV9c0DWGxwYvlcED/NlhTM0aDHM9yb0Y3Q0b0NQwiyrahX+qlck/Wo8fJQ7GHAkFn8MtczvAQzbLszyo+w0Q=="],
|
||||
|
||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.990.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.30", "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-xTEaPjZwOqVjGbLOP7qzwbdOWJOo1ne2mUhTZwEBBkPvNk4aXB/vcYwWwrjoSWUqtit4+GDbO75ePc/S6TUJYQ=="],
|
||||
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.973.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/xml-builder": "^3.972.4", "@smithy/core": "^3.23.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-4u/FbyyT3JqzfsESI70iFg6e2yp87MB5kS2qcxIA66m52VSTN1fvuvbCY1h/LKq1LvuxIrlJ1ItcyjvcKoaPLg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-r91OOPAcHnLCSxaeu/lzZAVRCZ/CtTNuwmJkUwpwSDshUrP7bkX1OmFn2nUMWd9kN53Q4cEo8b7226G4olt2Mg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.10", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-DTtuyXSWB+KetzLcWaSahLJCtTUe/3SXtlGp4ik9PCe9xD6swHEkG8n8/BNsQ9dsihb9nhFvuUB4DpdBGDcvVg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/credential-provider-env": "^3.972.8", "@aws-sdk/credential-provider-http": "^3.972.10", "@aws-sdk/credential-provider-login": "^3.972.8", "@aws-sdk/credential-provider-process": "^3.972.8", "@aws-sdk/credential-provider-sso": "^3.972.8", "@aws-sdk/credential-provider-web-identity": "^3.972.8", "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n2dMn21gvbBIEh00E8Nb+j01U/9rSqFIamWRdGm/mE5e+vHQ9g0cBNdrYFlM6AAiryKVHZmShWT9D1JAWJ3ISw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-rMFuVids8ICge/X9DF5pRdGMIvkVhDV9IQFQ8aTYk6iF0rl9jOUa1C3kjepxiXUlpgJQT++sLZkT9n0TMLHhQw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.9", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.8", "@aws-sdk/credential-provider-http": "^3.972.10", "@aws-sdk/credential-provider-ini": "^3.972.8", "@aws-sdk/credential-provider-process": "^3.972.8", "@aws-sdk/credential-provider-sso": "^3.972.8", "@aws-sdk/credential-provider-web-identity": "^3.972.8", "@aws-sdk/types": "^3.973.1", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LfJfO0ClRAq2WsSnA9JuUsNyIicD2eyputxSlSL0EiMrtxOxELLRG6ZVYDf/a1HCepaYPXeakH4y8D5OLCauag=="],
|
||||
|
||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-6cg26ffFltxM51OOS8NH7oE41EccaYiNlbd5VgUYwhiGCySLfHoGuGrLm2rMB4zhy+IO5nWIIG0HiodX8zdvHA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.8", "", { "dependencies": { "@aws-sdk/client-sso": "3.990.0", "@aws-sdk/core": "^3.973.10", "@aws-sdk/token-providers": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-35kqmFOVU1n26SNv+U37sM8b2TzG8LyqAcd6iM9gprqxyHEh/8IM3gzN4Jzufs3qM6IrH8e43ryZWYdvfVzzKQ=="],
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.8", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CZhN1bOc1J3ubQPqbmr5b4KaMJBgdDvYsmEIZuX++wFlzmZsKj1bwkaiTEb5U2V7kXuzLlpF5HJSOM9eY/6nGA=="],
|
||||
|
||||
"@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-xEmd3dnyn83K6t4AJxBJA63wpEoCD45ERFG0XMTViD2E/Ohls9TLxjOWPb1PAxR9/46cKy/TImez1GoqP6xVNQ=="],
|
||||
|
||||
"@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-pbvZ6Ye/Ks6BAZPa3RhsNjHrvxU9li25PMhSdDpbX0jzdpKpAkIR65gXSNKmA/REnSdEMWSD4vKUW+5eMFzB6w=="],
|
||||
|
||||
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aknPTb2M+G3s+0qLCx4Li/qGZH8IIYjugHMv15JTYMe6mgZO8VBpYgeGYsNMGCqCZOcWzuf900jFBG5bopfzmA=="],
|
||||
|
||||
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-Ftg09xNNRqaz9QNzlfdQWfpqMCJbsQdnZVJP55jfhbKi1+FTWxGuvfPoBhDHIovqWKjqbuiew3HuhxbJ0+OjgA=="],
|
||||
|
||||
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PY57QhzNuXHnwbJgbWYTrqIDHYSeOlhfYERTAuc16LKZpTZRJUjzBFokp9hF7u1fuGeE3D70ERXzdbMBOqQz7Q=="],
|
||||
|
||||
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.10", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.990.0", "@smithy/core": "^3.23.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-bBEL8CAqPQkI91ZM5a9xnFAzedpzH6NYCOtNyLarRAzTUTFN2DKqaC60ugBa7pnU1jSi4mA7WAXBsrod7nJltg=="],
|
||||
|
||||
"@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.6", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-format-url": "^3.972.3", "@smithy/eventstream-codec": "^4.2.8", "@smithy/eventstream-serde-browser": "^4.2.8", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-1DedO6N3m8zQ/vG6twNiHtsdwBgk773VdavLEbB3NXeKZDlzSK1BTviqWwvJdKx5UnIy4kGGP6WWpCEFEt/bhQ=="],
|
||||
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.990.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.990.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.8", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-retry": "^4.4.31", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.30", "@smithy/util-defaults-mode-node": "^4.2.33", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-3NA0s66vsy8g7hPh36ZsUgO4SiMyrhwcYvuuNK1PezO52vX3hXDW4pQrC6OQLGKGJV0o6tbEyQtXb/mPs8zg8w=="],
|
||||
|
||||
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-v4J8qYAWfOMcZ4MJUyatntOicTzEMaU7j3OpkRCGGFSL2NgXQ5VbxauIyORA+pxdKZ0qQG2tCQjQjZDlXEC3Ow=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.990.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.10", "@aws-sdk/nested-clients": "3.990.0", "@aws-sdk/types": "^3.973.1", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-L3BtUb2v9XmYgQdfGBzbBtKMXaP5fV973y3Qdxeevs6oUTVXFmi/mV1+LnScA/1wVPJC9/hlK+1o5vbt7cG7EQ=="],
|
||||
|
||||
"@aws-sdk/types": ["@aws-sdk/types@3.973.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwHBiMNOB468JiX6+i34c+THsKHErYUdNQ3HexeXZvVn4zouLjgaS4FejiGSi2HyBuzuyHg7SuOPmjSvoU9NRg=="],
|
||||
|
||||
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.990.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-kVwtDc9LNI3tQZHEMNbkLIOpeDK8sRSTuT8eMnzGY+O+JImPisfSTjdh+jw9OTznu+MYZjQsv0258sazVKunYg=="],
|
||||
|
||||
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-n7F2ycckcKFXa01vAsT/SJdjFHfKH9s96QHcs5gn8AaaigASICeME8WdUL9uBp8XV/OVwEt8+6gzn6KFUgQa8g=="],
|
||||
|
||||
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.4", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-H1onv5SkgPBK2P6JR2MjGgbOnttoNzSPIRoeZTNPZYyaplwGg50zS3amXvXqF0/qfXpWEC9rLWU564QTB9bSog=="],
|
||||
|
||||
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.3", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JurOwkRUcXD/5MTDBcqdyQ9eVedtAsZgw5rBwktsPTN7QtPiS2Ld1jkJepNgYoCufz1Wcut9iup7GJDoIHp8Fw=="],
|
||||
|
||||
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.8", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.10", "@aws-sdk/types": "^3.973.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-XJZuT0LWsFCW1C8dEpPAXSa7h6Pb3krr2y//1X0Zidpcl0vmgY5nL/X0JuBZlntpBzaN3+U4hvKjuijyiiR8zw=="],
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.4", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.3.4", "tslib": "^2.6.2" } }, "sha512-0zJ05ANfYqI6+rGqj8samZBFod0dPPousBjLEqg8WdxSgbMAkRgLyn81lP215Do0rFJ/17LIXwr7q0yK24mP6Q=="],
|
||||
|
||||
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
|
||||
|
||||
"@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="],
|
||||
|
||||
"@google/genai": ["@google/genai@1.41.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^7.1.1", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-S4WGil+PG0NBQRAx+0yrQuM/TWOLn2gGEy5wn4IsoOI6ouHad0P61p3OWdhJ3aqr9kfj8o904i/jevfaGoGuIQ=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
|
||||
|
||||
"@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.2", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.2", "@mariozechner/clipboard-darwin-universal": "0.3.2", "@mariozechner/clipboard-darwin-x64": "0.3.2", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.2", "@mariozechner/clipboard-linux-arm64-musl": "0.3.2", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-gnu": "0.3.2", "@mariozechner/clipboard-linux-x64-musl": "0.3.2", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.2", "@mariozechner/clipboard-win32-x64-msvc": "0.3.2" } }, "sha512-IHQpksNjo7EAtGuHFU+tbWDp5LarH3HU/8WiB9O70ZEoBPHOg0/6afwSLK0QyNMMmx4Bpi/zl6+DcBXe95nWYA=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uBf6K7Je1ihsgvmWxA8UCGCeI+nbRVRXoarZdLjl6slz94Zs1tNKFZqx7aCI5O1i3e0B6ja82zZ06BWrl0MCVw=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.2", "", { "os": "darwin" }, "sha512-mxSheKTW2U9LsBdXy0SdmdCAE5HqNS9QUmpNHLnfJ+SsbFKALjEZc5oRrVMXxGQSirDvYf5bjmRyT0QYYonnlg=="],
|
||||
|
||||
"@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-U1BcVEoidvwIp95+HJswSW+xr28EQiHR7rZjH6pn8Sja5yO4Yoe3yCN0Zm8Lo72BbSOK/fTSq0je7CJpaPCspg=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-BsinwG3yWTIjdgNCxsFlip7LkfwPk+ruw/aFCXHUg/fb5XC/Ksp+YMQ7u0LUtiKzIv/7LMXgZInJQH6gxbAaqQ=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-0/Gi5Xq2V6goXBop19ePoHvXsmJD9SzFlO3S+d6+T2b+BlPcpOu3Oa0wTjl+cZrLAAEzA86aPNBI+VVAFDFPKw=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-2AFFiXB24qf0zOZsxI1GJGb9wQGlOJyN6UwoXqmKS3dpQi/l6ix30IzDDA4c4ZcCcx4D+9HLYXhC1w7Sov8pXA=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-v6fVnsn7WMGg73Dab8QMwyFce7tzGfgEixKgzLP8f1GJqkJZi5zO4k4FOHzSgUufgLil63gnxvMpjWkgfeQN7A=="],
|
||||
|
||||
"@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-xVUtnoMQ8v2JVyfJLKKXACA6avdnchdbBkTsZs8BgJQo29qwCp5NIHAUO8gbJ40iaEGToW5RlmVk2M9V0HsHEw=="],
|
||||
|
||||
"@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-AEgg95TNi8TGgak2wSXZkXKCvAUTjWoU1Pqb0ON7JHrX78p616XUFNTJohtIon3e0w6k0pYPZeCuqRCza/Tqeg=="],
|
||||
|
||||
"@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-tGRuYpZwDOD7HBrCpyRuhGnHHSCknELvqwKKUG4JSfSB7JIU7LKRh6zx6fMUOQd8uISK35TjFg5UcNih+vJhFA=="],
|
||||
|
||||
"@mariozechner/jiti": ["@mariozechner/jiti@2.6.5", "", { "dependencies": { "std-env": "^3.10.0", "yoctocolors": "^2.1.2" }, "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-faGUlTcXka5l7rv0lP3K3vGW/ejRuOS24RR2aSFWREUQqzjgdsuWNo/IiPqL3kWRGt6Ahl2+qcDAwtdeWeuGUw=="],
|
||||
|
||||
"@mariozechner/pi-agent-core": ["@mariozechner/pi-agent-core@0.52.12", "", { "dependencies": { "@mariozechner/pi-ai": "^0.52.12" } }, "sha512-fBQdwLMvTteHUP9nJxMjtMpEHH4I8tdGnkerOoCFnS9y03AHdqy96IhtL+zZjw9N3dmVCOVqh8gwGjAGLZT31Q=="],
|
||||
|
||||
"@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.52.12", "", { "dependencies": { "@anthropic-ai/sdk": "^0.73.0", "@aws-sdk/client-bedrock-runtime": "^3.983.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.10.0", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "chalk": "^5.6.2", "openai": "6.10.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-oF7OMJu1aUx7MXJeJoJ/3JDXzD2a5SqK9nHVK3mCA8DRQaykv9g+wcFZaANcCl0vAR2QSDr5KN3ZMARlFNWiVg=="],
|
||||
|
||||
"@mariozechner/pi-coding-agent": ["@mariozechner/pi-coding-agent@0.52.12", "", { "dependencies": { "@mariozechner/jiti": "^2.6.2", "@mariozechner/pi-agent-core": "^0.52.12", "@mariozechner/pi-ai": "^0.52.12", "@mariozechner/pi-tui": "^0.52.12", "@silvia-odwyer/photon-node": "^0.3.4", "chalk": "^5.5.0", "cli-highlight": "^2.1.11", "diff": "^8.0.2", "file-type": "^21.1.1", "glob": "^13.0.1", "hosted-git-info": "^9.0.2", "ignore": "^7.0.5", "marked": "^15.0.12", "minimatch": "^10.1.1", "proper-lockfile": "^4.1.2", "yaml": "^2.8.2" }, "optionalDependencies": { "@mariozechner/clipboard": "^0.3.2" }, "bin": { "pi": "dist/cli.js" } }, "sha512-6Zmh57vUoRiN+rfRJxWErII/CNC5/3yX5nCU7tK+Eud2Ko+RcVZoBccwjdIUzsJib3Liw/yv9T1EWvz6ZdGbhw=="],
|
||||
|
||||
"@mariozechner/pi-tui": ["@mariozechner/pi-tui@0.52.12", "", { "dependencies": { "@types/mime-types": "^2.1.4", "chalk": "^5.5.0", "get-east-asian-width": "^1.3.0", "marked": "^15.0.12", "mime-types": "^3.0.1" } }, "sha512-QQ4LUlAYKN2BvT3EMU63+kYLlIkyr706+rUFBGWvkiT8ZyMy5if3oaVJpO5qAndsMB+MaUnttIBPh3iHiaJ01g=="],
|
||||
|
||||
"@mistralai/mistralai": ["@mistralai/mistralai@1.10.0", "", { "dependencies": { "zod": "^3.20.0", "zod-to-json-schema": "^3.24.1" } }, "sha512-tdIgWs4Le8vpvPiUEWne6tK0qbVc+jMenujnvTqOjogrJUsCSQhus0tHTU1avDDh5//Rq2dFgP9mWRAdIEoBqg=="],
|
||||
|
||||
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="],
|
||||
|
||||
"@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="],
|
||||
|
||||
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw=="],
|
||||
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.23.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.12", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-Yq4UPVoQICM9zHnByLmG8632t2M0+yap4T7ANVw482J0W7HW0pOuxwVmeOwzJqX2Q89fkXz0Vybz55Wj2Xzrsg=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="],
|
||||
|
||||
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.8", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw=="],
|
||||
|
||||
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw=="],
|
||||
|
||||
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ=="],
|
||||
|
||||
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.8", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A=="],
|
||||
|
||||
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.8", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ=="],
|
||||
|
||||
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA=="],
|
||||
|
||||
"@smithy/hash-node": ["@smithy/hash-node@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA=="],
|
||||
|
||||
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ=="],
|
||||
|
||||
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
|
||||
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="],
|
||||
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.14", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FUFNE5KVeaY6U/GL0nzAAHkaCHzXLZcY1EhtQnsAqhD8Du13oPKtMB9/0WK4/LK6a/T5OZ24wPoSShff5iI6Ag=="],
|
||||
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.31", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-RXBzLpMkIrxBPe4C8OmEOHvS8aH9RUuCOH++Acb5jZDEblxDjyg6un72X9IcbrGTJoiUwmI7hLypNfuDACypbg=="],
|
||||
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="],
|
||||
|
||||
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA=="],
|
||||
|
||||
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.8", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg=="],
|
||||
|
||||
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.10", "", { "dependencies": { "@smithy/abort-controller": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/querystring-builder": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-u4YeUwOWRZaHbWaebvrs3UhwQwj+2VNmcVCwXcYTvPIuVyM7Ex1ftAj+fdbG/P4AkBwLq/+SKn+ydOI4ZJE9PA=="],
|
||||
|
||||
"@smithy/property-provider": ["@smithy/property-provider@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w=="],
|
||||
|
||||
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ=="],
|
||||
|
||||
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw=="],
|
||||
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA=="],
|
||||
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0" } }, "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ=="],
|
||||
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.3", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="],
|
||||
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.11.3", "", { "dependencies": { "@smithy/core": "^3.23.0", "@smithy/middleware-endpoint": "^4.4.14", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.12", "tslib": "^2.6.2" } }, "sha512-Q7kY5sDau8OoE6Y9zJoRGgje8P4/UY0WzH8R2ok0PDh+iJ+ZnEKowhjEqYafVcubkbYxQVaqwm3iufktzhprGg=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="],
|
||||
|
||||
"@smithy/url-parser": ["@smithy/url-parser@4.2.8", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA=="],
|
||||
|
||||
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
|
||||
|
||||
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
|
||||
|
||||
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
|
||||
|
||||
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="],
|
||||
|
||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
|
||||
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.30", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cMni0uVU27zxOiU8TuC8pQLC1pYeZ/xEMxvchSK/ILwleRd1ugobOcIRr5vXtcRqKd4aBLWlpeBoDPJJ91LQng=="],
|
||||
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.33", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.11.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-LEb2aq5F4oZUSzWBG7S53d4UytZSkOEJPXcBq/xbG2/TmK9EW5naUZ8lKu1BEyWMzdHIzEVN16M3k8oxDq+DJA=="],
|
||||
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="],
|
||||
|
||||
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
|
||||
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.8", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A=="],
|
||||
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.2.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg=="],
|
||||
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.5.12", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.10", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-D8tgkrmhAX/UNeCZbqbEO3uqyghUnEmmoO9YEvRuwxjlkKKUE7FOgCJnqpTlQPe9MApdWPky58mNQQHbnCzoNg=="],
|
||||
|
||||
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
|
||||
|
||||
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
|
||||
|
||||
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
|
||||
|
||||
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
|
||||
|
||||
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
||||
|
||||
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||
|
||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
|
||||
|
||||
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.2", "", { "dependencies": { "jackspeak": "^4.2.3" } }, "sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"basic-ftp": ["basic-ftp@5.1.0", "", {}, "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
"cli-highlight": ["cli-highlight@2.1.11", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="],
|
||||
|
||||
"cliui": ["cliui@7.0.4", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
|
||||
|
||||
"diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
||||
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||
|
||||
"fast-xml-parser": ["fast-xml-parser@5.3.4", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="],
|
||||
|
||||
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
|
||||
|
||||
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||
|
||||
"gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
|
||||
|
||||
"gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
||||
|
||||
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
|
||||
|
||||
"glob": ["glob@13.0.3", "", { "dependencies": { "minimatch": "^10.2.0", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA=="],
|
||||
|
||||
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
|
||||
|
||||
"google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="],
|
||||
|
||||
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
|
||||
|
||||
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"is-network-error": ["is-network-error@1.3.0", "", {}, "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
|
||||
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||
|
||||
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.0", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
|
||||
|
||||
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
|
||||
|
||||
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||
|
||||
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"openai": ["openai@6.10.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A=="],
|
||||
|
||||
"p-retry": ["p-retry@7.1.1", "", { "dependencies": { "is-network-error": "^1.1.0" } }, "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w=="],
|
||||
|
||||
"pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
|
||||
|
||||
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
|
||||
|
||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||
|
||||
"parse5": ["parse5@5.1.1", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@6.0.1", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="],
|
||||
|
||||
"partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
|
||||
|
||||
"proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="],
|
||||
|
||||
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
|
||||
|
||||
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
||||
|
||||
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||
|
||||
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
|
||||
|
||||
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
|
||||
|
||||
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
|
||||
|
||||
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
|
||||
|
||||
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
||||
|
||||
"token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
|
||||
|
||||
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||
|
||||
"undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
|
||||
"yargs": ["yargs@16.2.0", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
|
||||
|
||||
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"cli-highlight/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"hosted-git-info/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||
|
||||
"node-fetch/data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
|
||||
|
||||
"path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
|
||||
|
||||
"rimraf/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||
|
||||
"rimraf/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
|
||||
"rimraf/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"rimraf/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"rimraf/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "pi-perplexity",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"pi": {
|
||||
"extensions": ["./src/index.ts"]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mariozechner/pi-tui": "*",
|
||||
"@mariozechner/pi-ai": "*",
|
||||
"@sinclair/typebox": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mariozechner/pi-coding-agent": "*",
|
||||
"@mariozechner/pi-tui": "*",
|
||||
"@mariozechner/pi-ai": "*",
|
||||
"@sinclair/typebox": "*",
|
||||
"bun-types": "*",
|
||||
"typescript": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test": "bun test"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
# pi-perplexity Implementation Plan
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Bun installed
|
||||
- oh-my-pi installed (for testing the plugin)
|
||||
- Perplexity macOS app installed and logged in (for Path 1 auth), OR a Perplexity account email (for Path 2 OTP auth)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Project Scaffold
|
||||
|
||||
### 1.1 Initialize package
|
||||
|
||||
```
|
||||
pi-perplexity/
|
||||
package.json
|
||||
tsconfig.json
|
||||
src/
|
||||
index.ts
|
||||
```
|
||||
|
||||
**package.json** — must include `omp` (or `pi`) manifest field:
|
||||
```json
|
||||
{
|
||||
"name": "pi-perplexity",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"omp": {
|
||||
"name": "Perplexity Search",
|
||||
"description": "Web search via Perplexity Pro/Max subscription (OAuth)",
|
||||
"tools": "src/index.ts",
|
||||
"settings": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Perplexity account email (for OTP login)",
|
||||
"env": "PERPLEXITY_EMAIL"
|
||||
}
|
||||
}
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@oh-my-pi/pi-tui": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@oh-my-pi/pi-tui": "workspace:*",
|
||||
"@oh-my-pi/pi-agent-core": "workspace:*",
|
||||
"@sinclair/typebox": "*",
|
||||
"typescript": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**tsconfig.json** — extend from oh-my-pi root or standalone:
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
```
|
||||
|
||||
**src/index.ts** — skeleton factory:
|
||||
```typescript
|
||||
import type { CustomToolFactory } from "@oh-my-pi/pi-coding-agent/extensibility";
|
||||
|
||||
const factory: CustomToolFactory = (api) => {
|
||||
return {
|
||||
name: "perplexity_search",
|
||||
label: "Perplexity Search",
|
||||
description: "...",
|
||||
parameters: api.typebox.Type.Object({
|
||||
query: api.typebox.Type.String({ description: "Search query" }),
|
||||
}),
|
||||
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
||||
return { content: [{ type: "text", text: "TODO" }] };
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default factory;
|
||||
```
|
||||
|
||||
### Acceptance
|
||||
- `bun run --bun src/index.ts` doesn't crash
|
||||
- Factory returns a valid CustomTool shape
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Auth — JWT Acquisition and Storage
|
||||
|
||||
### 2.1 JWT utilities (`src/auth/jwt.ts`)
|
||||
|
||||
Implement:
|
||||
- `decodeJwtExpiry(token: string): number` — base64url decode payload, extract `exp` claim, return ms with 5-min margin. Fallback: now + 1 hour.
|
||||
- `isJwtExpired(token: string, bufferMs?: number): boolean`
|
||||
|
||||
No dependencies. Use `atob` or `Buffer.from(payload, "base64url")`.
|
||||
|
||||
### 2.2 Token storage (`src/auth/storage.ts`)
|
||||
|
||||
Implement:
|
||||
- `loadToken(): Promise<StoredToken | null>` — read from `~/.config/pi-perplexity/auth.json`
|
||||
- `saveToken(token: StoredToken): Promise<void>` — write with `0600` permissions
|
||||
- `clearToken(): Promise<void>` — delete file
|
||||
|
||||
```typescript
|
||||
interface StoredToken {
|
||||
jwt: string;
|
||||
expires: number;
|
||||
email?: string;
|
||||
acquiredAt: number;
|
||||
}
|
||||
```
|
||||
|
||||
Use `Bun.write()` and `Bun.file()`. Handle ENOENT on read (no stored token).
|
||||
|
||||
### 2.3 Login flow (`src/auth/login.ts`)
|
||||
|
||||
Implement:
|
||||
- `extractFromDesktopApp(): Promise<string | null>` — `defaults read ai.perplexity.mac authToken` via `Bun.$`. macOS only, returns null on other platforms or if app not installed.
|
||||
- `loginViaEmailOtp(promptFn): Promise<string>` — three-step HTTP flow (CSRF -> send OTP -> verify OTP). Uses `fetch` with the required headers.
|
||||
- `authenticate(promptFn): Promise<StoredToken>` — tries desktop extraction, falls back to email OTP, saves result.
|
||||
|
||||
Constants:
|
||||
```typescript
|
||||
const USER_AGENT = "Perplexity/641 CFNetwork/1568 Darwin/25.2.0";
|
||||
const API_VERSION = "2.18";
|
||||
```
|
||||
|
||||
### Acceptance
|
||||
- Can extract JWT from macOS app (if installed)
|
||||
- Can complete email OTP flow (manual test)
|
||||
- Token persists across process restarts
|
||||
- Expired tokens detected correctly
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: SSE Client and Event Merging
|
||||
|
||||
### 3.1 Type definitions (`src/search/types.ts`)
|
||||
|
||||
Define all types from architecture.md:
|
||||
- `StreamEvent`, `StreamBlock`, `MarkdownBlock`, `WebResult`, `StreamSource`
|
||||
- `SearchResult` (unified output: answer, sources, model, requestId)
|
||||
- `SearchSource` (title, url, snippet, publishedDate, ageSeconds)
|
||||
- `SearchParams` (query, recency, limit, signal)
|
||||
- `SearchError` class
|
||||
|
||||
### 3.2 SSE stream parser (`src/search/stream.ts`)
|
||||
|
||||
Implement:
|
||||
- `async function* readSseEvents<T>(body: ReadableStream<Uint8Array>, signal?: AbortSignal): AsyncGenerator<T>` — reads SSE `data:` lines, parses JSON, yields typed events. Handles `[DONE]` marker. Abort-aware via signal.
|
||||
|
||||
This is the core utility. Parse byte stream line by line, detect `data:` prefix, accumulate multi-line data fields, parse JSON.
|
||||
|
||||
### 3.3 Event merging (`src/search/stream.ts`)
|
||||
|
||||
Implement:
|
||||
- `mergeMarkdownBlock(existing, incoming): MarkdownBlock` — handle `chunk_starting_offset` splice logic
|
||||
- `mergeBlocks(existing, incoming): StreamBlock[]` — key by `intended_usage`, merge markdown blocks
|
||||
- `mergeEvent(existing, incoming): StreamEvent` — shallow merge top-level, delegate blocks, preserve sources
|
||||
|
||||
### 3.4 Search client (`src/search/client.ts`)
|
||||
|
||||
Implement:
|
||||
- `searchPerplexity(params: SearchParams, jwt: string): Promise<SearchResult>`
|
||||
|
||||
Steps:
|
||||
1. Build request body (query, params object with all required fields)
|
||||
2. `fetch` POST to `https://www.perplexity.ai/rest/sse/perplexity_ask` with required headers
|
||||
3. Iterate SSE events via `readSseEvents()`, merge incrementally
|
||||
4. On stream end, extract answer (markdown blocks -> ask_text -> text fallback)
|
||||
5. Extract sources (web_results block -> sources_list fallback), deduplicate by URL
|
||||
6. Return `SearchResult`
|
||||
|
||||
### Acceptance
|
||||
- Given a valid JWT, returns answer + sources for a test query
|
||||
- Handles stream errors (error_code in event)
|
||||
- Handles abort signal
|
||||
- Empty results return gracefully
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Response Formatting
|
||||
|
||||
### 4.1 LLM output formatter (`src/search/format.ts`)
|
||||
|
||||
Implement:
|
||||
- `formatForLLM(result: SearchResult): string`
|
||||
|
||||
Output format:
|
||||
```
|
||||
## Answer
|
||||
<answer text>
|
||||
|
||||
## Sources
|
||||
N sources
|
||||
[1] Title (2d ago)
|
||||
https://url
|
||||
snippet preview...
|
||||
...
|
||||
|
||||
## Meta
|
||||
Provider: perplexity (oauth)
|
||||
Model: <display_model>
|
||||
Request: <uuid>
|
||||
```
|
||||
|
||||
- Age calculation: `(Date.now() - new Date(dateStr).getTime()) / 1000` -> human-readable ("2d ago", "3h ago", "just now")
|
||||
- Truncate snippets to 240 chars
|
||||
- Respect `limit` param for source count
|
||||
|
||||
### Acceptance
|
||||
- Output is clean, readable, parseable by LLM
|
||||
- Sources numbered and linked
|
||||
- Age formatting works for various date formats
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Plugin Integration
|
||||
|
||||
### 5.1 Tool definition (`src/index.ts`)
|
||||
|
||||
Wire everything together:
|
||||
|
||||
```typescript
|
||||
const factory: CustomToolFactory = (api) => {
|
||||
const { Type } = api.typebox;
|
||||
|
||||
return {
|
||||
name: "perplexity_search",
|
||||
label: "Perplexity Search",
|
||||
description: "...", // from a .md file or inline
|
||||
parameters: Type.Object({
|
||||
query: Type.String({ description: "Search query" }),
|
||||
recency: Type.Optional(
|
||||
Type.Union([
|
||||
Type.Literal("hour"),
|
||||
Type.Literal("day"),
|
||||
Type.Literal("week"),
|
||||
Type.Literal("month"),
|
||||
Type.Literal("year"),
|
||||
], { description: "Filter results by recency" })
|
||||
),
|
||||
limit: Type.Optional(
|
||||
Type.Number({ description: "Max sources to return", minimum: 1, maximum: 50 })
|
||||
),
|
||||
}),
|
||||
|
||||
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
||||
// 1. Get or refresh JWT
|
||||
// 2. Call searchPerplexity()
|
||||
// 3. Format result
|
||||
// 4. Return { content: [{ type: "text", text }], details }
|
||||
},
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### 5.2 Tool description prompt
|
||||
|
||||
Create `src/prompts/tool-description.md`:
|
||||
```markdown
|
||||
# Perplexity Search
|
||||
|
||||
Search the web using Perplexity Pro with multi-step reasoning and source citations.
|
||||
|
||||
<instruction>
|
||||
- Use for questions requiring up-to-date web information
|
||||
- Prefer primary sources; corroborate claims across multiple results
|
||||
- Include source links in your response
|
||||
</instruction>
|
||||
|
||||
<output>
|
||||
Returns synthesized answer with numbered source citations, URLs, and snippets.
|
||||
</output>
|
||||
|
||||
<params>
|
||||
- query: Search query (required)
|
||||
- recency: Filter by time — hour, day, week, month, year (optional)
|
||||
- limit: Maximum number of sources to return (optional)
|
||||
</params>
|
||||
```
|
||||
|
||||
### 5.3 TUI rendering (optional, can defer)
|
||||
|
||||
If TUI rendering is desired:
|
||||
- `renderCall(args, theme)` — show query text and recency filter
|
||||
- `renderResult(result, options, theme)` — show answer preview, source count, expandable source list
|
||||
|
||||
This requires importing `@oh-my-pi/pi-tui` Component types. Can be added later; the tool works without it (omp shows raw text).
|
||||
|
||||
### Acceptance
|
||||
- Plugin loads when registered with omp (`omp plugin install ./pi-perplexity`)
|
||||
- Agent sees `perplexity_search` tool
|
||||
- Agent can invoke tool and gets results
|
||||
- Results display in TUI
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Login Command (optional)
|
||||
|
||||
### 6.1 Custom command for `omp login perplexity`
|
||||
|
||||
If the plugin should expose a login command:
|
||||
|
||||
Create `src/commands/login.ts`:
|
||||
```typescript
|
||||
// CustomCommand that triggers the auth flow interactively
|
||||
```
|
||||
|
||||
Register in package.json manifest:
|
||||
```json
|
||||
"omp": {
|
||||
"commands": ["src/commands/login.ts"]
|
||||
}
|
||||
```
|
||||
|
||||
This allows `omp perplexity login` or similar. If not needed, the tool's `execute` can trigger login on first use.
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Testing
|
||||
|
||||
### 7.1 Unit tests
|
||||
|
||||
- `auth/jwt.test.ts` — JWT decode, expiry extraction, edge cases (malformed, missing exp)
|
||||
- `search/stream.test.ts` — SSE parsing with fixture data, event merging logic
|
||||
- `search/format.test.ts` — LLM output formatting, age calculation, truncation
|
||||
|
||||
### 7.2 Integration tests (manual)
|
||||
|
||||
- End-to-end: authenticate -> search -> verify answer and sources returned
|
||||
- Expired JWT handling: mock expired token, verify re-auth prompt
|
||||
- Abort: cancel mid-stream, verify clean exit
|
||||
- Error responses: 401, 429, stream errors
|
||||
|
||||
### 7.3 Fixture data
|
||||
|
||||
Capture real SSE responses from Perplexity for test fixtures:
|
||||
```typescript
|
||||
// test/fixtures/sse-response.txt — raw SSE stream
|
||||
// test/fixtures/merged-event.json — expected merged result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Order
|
||||
|
||||
```
|
||||
Phase 1: Scaffold (~30 min) — package.json, tsconfig, skeleton factory
|
||||
Phase 2: Auth (~2 hours) — JWT decode, storage, login flow
|
||||
Phase 3: SSE Client (~3 hours) — stream parser, event merging, search client
|
||||
Phase 4: Formatting (~1 hour) — LLM output formatting
|
||||
Phase 5: Plugin Wiring (~1 hour) — connect auth + search + format in factory
|
||||
Phase 6: Login Command (~30 min) — optional interactive login command
|
||||
Phase 7: Testing (~2 hours) — unit tests + manual integration
|
||||
```
|
||||
|
||||
Total estimate: ~10 hours
|
||||
|
||||
### Critical path
|
||||
|
||||
Phase 3 (SSE parsing + event merging) is the most complex and error-prone component. The incremental merge logic for markdown chunks with offset splicing needs careful testing with real Perplexity SSE data. Capture fixtures early.
|
||||
|
||||
### Risk areas
|
||||
|
||||
1. **Perplexity API instability** — this is a reverse-engineered internal API, not a public contract. Headers, body format, or SSE event schema could change without notice. Mitigate by keeping the client thin and the types loose (optional fields everywhere).
|
||||
|
||||
2. **JWT expiry** — Perplexity JWTs from the desktop app may have varying lifetimes. The 5-minute buffer should handle most cases, but monitor for short-lived tokens.
|
||||
|
||||
3. **macOS-only desktop extraction** — Path 1 only works on macOS. Linux/Windows users must use email OTP. This is acceptable for the initial version.
|
||||
|
||||
4. **Cloudflare challenges** — Perplexity uses Cloudflare. The specific User-Agent and headers bypass managed challenges (reverse-engineered from the macOS app). If Cloudflare changes rules, this may break.
|
||||
@@ -0,0 +1,37 @@
|
||||
const FIVE_MINUTES_MS = 5 * 60 * 1000;
|
||||
const ONE_HOUR_MS = 60 * 60 * 1000;
|
||||
|
||||
function decodeBase64Url(input: string): string {
|
||||
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
||||
return Buffer.from(padded, "base64").toString("utf8");
|
||||
}
|
||||
|
||||
/** Decode JWT payload and extract expiry as epoch ms (with 5min safety margin). Returns fallback of now+1h on decode failure. */
|
||||
export function decodeJwtExpiry(token: string): number {
|
||||
const fallback = Date.now() + ONE_HOUR_MS;
|
||||
|
||||
try {
|
||||
const payload = token.split(".")[1];
|
||||
if (!payload) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const decodedPayload = decodeBase64Url(payload);
|
||||
const parsed = JSON.parse(decodedPayload) as { exp?: unknown };
|
||||
|
||||
if (typeof parsed.exp !== "number" || !Number.isFinite(parsed.exp)) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const expiryMs = parsed.exp * 1000 - FIVE_MINUTES_MS;
|
||||
return Number.isFinite(expiryMs) ? expiryMs : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the token is expired (with optional buffer). */
|
||||
export function isJwtExpired(token: string, bufferMs = 0): boolean {
|
||||
return decodeJwtExpiry(token) <= Date.now() + bufferMs;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { AuthError } from "../search/types.js";
|
||||
import { decodeJwtExpiry, isJwtExpired } from "./jwt.js";
|
||||
import { clearToken, loadToken, saveToken } from "./storage.js";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const DESKTOP_AUTH_HELP =
|
||||
"Install the Perplexity desktop app and sign in, or set PI_AUTH_NO_BORROW=1 to skip desktop token borrowing.";
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
/** Extract JWT from macOS Perplexity desktop app via `defaults read`. Returns null if app not installed or not logged in. */
|
||||
export async function extractFromDesktopApp(): Promise<string | null> {
|
||||
if (process.platform !== "darwin") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout } = await execFileAsync("defaults", ["read", "ai.perplexity.mac", "authToken"]);
|
||||
const token = stdout.trim();
|
||||
if (!token || token.split(".").length !== 3) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return token;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Run MVP auth strategy: load cached → try desktop extraction → save → throw AuthError if all fail. */
|
||||
export async function authenticate(): Promise<string> {
|
||||
const cached = await loadToken();
|
||||
if (cached) {
|
||||
if (!isJwtExpired(cached.access)) {
|
||||
return cached.access;
|
||||
}
|
||||
|
||||
await clearToken();
|
||||
|
||||
if (process.env.PI_AUTH_NO_BORROW === "1") {
|
||||
throw new AuthError(
|
||||
"EXPIRED",
|
||||
`Cached token is expired. Re-authenticate in Perplexity desktop app, then retry. ${DESKTOP_AUTH_HELP}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.PI_AUTH_NO_BORROW === "1") {
|
||||
throw new AuthError(
|
||||
"NO_TOKEN",
|
||||
`No valid cached token found and desktop token borrowing is disabled. ${DESKTOP_AUTH_HELP}`,
|
||||
);
|
||||
}
|
||||
|
||||
let desktopToken: string | null;
|
||||
try {
|
||||
desktopToken = await extractFromDesktopApp();
|
||||
} catch {
|
||||
throw new AuthError(
|
||||
"EXTRACTION_FAILED",
|
||||
`Failed to read token from the Perplexity desktop app. Ensure the app is installed and signed in. ${DESKTOP_AUTH_HELP}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!desktopToken) {
|
||||
throw new AuthError(
|
||||
"NO_TOKEN",
|
||||
`Could not find a desktop token. Ensure Perplexity desktop app is installed and signed in. ${DESKTOP_AUTH_HELP}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (isJwtExpired(desktopToken)) {
|
||||
throw new AuthError(
|
||||
"EXPIRED",
|
||||
`Desktop token is expired. Open Perplexity desktop app and sign in again, then retry. ${DESKTOP_AUTH_HELP}`,
|
||||
);
|
||||
}
|
||||
|
||||
await saveToken({
|
||||
type: "oauth",
|
||||
access: desktopToken,
|
||||
expires: decodeJwtExpiry(desktopToken),
|
||||
});
|
||||
|
||||
return desktopToken;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
import type { StoredToken } from "../search/types.js";
|
||||
|
||||
const TOKEN_PATH = join(homedir(), ".config", "pi-perplexity", "auth.json");
|
||||
|
||||
function isStoredToken(value: unknown): value is StoredToken {
|
||||
if (!value || typeof value !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidate = value as Partial<StoredToken>;
|
||||
return (
|
||||
candidate.type === "oauth" &&
|
||||
typeof candidate.access === "string" &&
|
||||
candidate.access.length > 0 &&
|
||||
typeof candidate.expires === "number" &&
|
||||
Number.isFinite(candidate.expires)
|
||||
);
|
||||
}
|
||||
|
||||
/** Load persisted token from ~/.config/pi-perplexity/auth.json. Returns null if missing or unreadable. */
|
||||
export async function loadToken(): Promise<StoredToken | null> {
|
||||
try {
|
||||
const raw = await readFile(TOKEN_PATH, "utf8");
|
||||
const parsed = JSON.parse(raw) as unknown;
|
||||
if (!isStoredToken(parsed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Save token to disk with 0600 permissions. Creates directory if needed. */
|
||||
export async function saveToken(token: StoredToken): Promise<void> {
|
||||
await mkdir(dirname(TOKEN_PATH), { recursive: true });
|
||||
await writeFile(TOKEN_PATH, `${JSON.stringify(token, null, 2)}\n`, "utf8");
|
||||
await chmod(TOKEN_PATH, 0o600);
|
||||
}
|
||||
|
||||
/** Delete the stored token file. No-op if missing. */
|
||||
export async function clearToken(): Promise<void> {
|
||||
await rm(TOKEN_PATH, { force: true });
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
import { StringEnum } from "@mariozechner/pi-ai";
|
||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
|
||||
import { authenticate } from "./auth/login.js";
|
||||
import { clearToken } from "./auth/storage.js";
|
||||
import { formatForLLM } from "./search/format.js";
|
||||
import { searchPerplexity } from "./search/client.js";
|
||||
import { AuthError, SearchError } from "./search/types.js";
|
||||
|
||||
export default function (pi: ExtensionAPI) {
|
||||
pi.registerTool({
|
||||
name: "perplexity_search",
|
||||
label: "Perplexity Search",
|
||||
description: "Search the web with your Perplexity subscription.",
|
||||
parameters: Type.Object({
|
||||
query: Type.String({ description: "Search query" }),
|
||||
recency: Type.Optional(
|
||||
StringEnum(["hour", "day", "week", "month", "year"] as const, {
|
||||
description: "Filter results by recency",
|
||||
}),
|
||||
),
|
||||
limit: Type.Optional(
|
||||
Type.Number({ description: "Max sources to return", minimum: 1, maximum: 50 }),
|
||||
),
|
||||
}),
|
||||
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
||||
const start = Date.now();
|
||||
let sourceCount = 0;
|
||||
|
||||
try {
|
||||
onUpdate?.({
|
||||
content: [{ type: "text", text: "Authenticating with Perplexity..." }],
|
||||
details: { toolCallId },
|
||||
});
|
||||
|
||||
const jwt = await authenticate();
|
||||
|
||||
if (signal?.aborted) {
|
||||
return {
|
||||
content: [{ type: "text", text: "Perplexity search was cancelled." }],
|
||||
details: { sourceCount: 0, queryMs: Date.now() - start },
|
||||
};
|
||||
}
|
||||
|
||||
onUpdate?.({
|
||||
content: [{ type: "text", text: "Querying Perplexity..." }],
|
||||
details: { toolCallId },
|
||||
});
|
||||
|
||||
const result = await searchPerplexity(
|
||||
{
|
||||
query: params.query,
|
||||
recency: params.recency,
|
||||
limit: params.limit,
|
||||
},
|
||||
jwt,
|
||||
signal,
|
||||
);
|
||||
|
||||
const formatted = formatForLLM(result, params.limit);
|
||||
sourceCount =
|
||||
typeof params.limit === "number"
|
||||
? Math.min(params.limit, result.sources.length)
|
||||
: result.sources.length;
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: formatted }],
|
||||
details: {
|
||||
model: result.displayModel,
|
||||
sourceCount,
|
||||
queryMs: Date.now() - start,
|
||||
uuid: result.uuid,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const queryMs = Date.now() - start;
|
||||
|
||||
if (error instanceof AuthError) {
|
||||
return {
|
||||
content: [{ type: "text", text: `Authentication failed: ${error.message}` }],
|
||||
details: { sourceCount, queryMs },
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof SearchError) {
|
||||
if (error.code === "AUTH") {
|
||||
await clearToken().catch(() => undefined);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: `Perplexity search failed: ${error.message}` }],
|
||||
details: { sourceCount, queryMs },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Perplexity search failed: ${(error as Error).message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
details: { sourceCount, queryMs },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
import { mergeEvent, readSseEvents } from "./stream.js";
|
||||
import type { SearchResult, StreamEvent, WebResult } from "./types.js";
|
||||
import { SearchError } from "./types.js";
|
||||
|
||||
const PERPLEXITY_ENDPOINT = "https://www.perplexity.ai/rest/sse/perplexity_ask";
|
||||
const PERPLEXITY_USER_AGENT = "Perplexity/641 CFNetwork/1568 Darwin/25.2.0";
|
||||
|
||||
export interface SearchParams {
|
||||
query: string;
|
||||
recency?: "hour" | "day" | "week" | "month" | "year";
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
function normalizeUrl(url: string): string {
|
||||
return url.trim().replace(/\/$/, "").toLowerCase();
|
||||
}
|
||||
|
||||
function dedupeSourcesByUrl(sources: WebResult[]): WebResult[] {
|
||||
const seen = new Set<string>();
|
||||
const deduped: WebResult[] = [];
|
||||
|
||||
for (const source of sources) {
|
||||
const url = source.url?.trim();
|
||||
if (!url) {
|
||||
deduped.push(source);
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = normalizeUrl(url);
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seen.add(key);
|
||||
deduped.push(source);
|
||||
}
|
||||
|
||||
return deduped;
|
||||
}
|
||||
|
||||
function extractTextFromBlock(event: StreamEvent, match: (usage: string) => boolean): string | null {
|
||||
const blocks = event.blocks ?? [];
|
||||
|
||||
for (const block of blocks) {
|
||||
const usage = block.intended_usage ?? "";
|
||||
if (!match(usage)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const markdown = block.markdown_block;
|
||||
if (!markdown) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof markdown.answer === "string" && markdown.answer.trim().length > 0) {
|
||||
return markdown.answer.trim();
|
||||
}
|
||||
|
||||
if (markdown.chunks && markdown.chunks.length > 0) {
|
||||
const chunkText = markdown.chunks.join("").trim();
|
||||
if (chunkText.length > 0) {
|
||||
return chunkText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function extractAnswer(event: StreamEvent): string {
|
||||
const markdownAnswer = extractTextFromBlock(event, (usage) => usage.includes("markdown"));
|
||||
if (markdownAnswer) {
|
||||
return markdownAnswer;
|
||||
}
|
||||
|
||||
const askTextAnswer = extractTextFromBlock(event, (usage) => usage === "ask_text");
|
||||
if (askTextAnswer) {
|
||||
return askTextAnswer;
|
||||
}
|
||||
|
||||
return event.text?.trim() ?? "";
|
||||
}
|
||||
|
||||
function extractSources(event: StreamEvent): WebResult[] {
|
||||
const webResultsBlock = (event.blocks ?? []).find(
|
||||
(block) => block.intended_usage === "web_results",
|
||||
);
|
||||
|
||||
const blockSources = webResultsBlock?.web_result_block?.web_results ?? [];
|
||||
if (blockSources.length > 0) {
|
||||
return dedupeSourcesByUrl(blockSources);
|
||||
}
|
||||
|
||||
const fallbackSources: WebResult[] = (event.sources_list ?? []).map((source) => ({
|
||||
name: source.title,
|
||||
url: source.url,
|
||||
snippet: source.snippet,
|
||||
timestamp: source.date,
|
||||
}));
|
||||
|
||||
return dedupeSourcesByUrl(fallbackSources);
|
||||
}
|
||||
|
||||
function buildRequestBody(params: SearchParams): Record<string, unknown> {
|
||||
const query = params.query;
|
||||
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
|
||||
|
||||
return {
|
||||
query_str: query,
|
||||
params: {
|
||||
query_str: query,
|
||||
search_focus: "internet",
|
||||
mode: "copilot",
|
||||
model_preference: "pplx_pro_upgraded",
|
||||
sources: ["web"],
|
||||
attachments: [],
|
||||
frontend_uuid: crypto.randomUUID(),
|
||||
frontend_context_uuid: crypto.randomUUID(),
|
||||
version: "2.18",
|
||||
language: "en-US",
|
||||
timezone,
|
||||
search_recency_filter: params.recency ?? null,
|
||||
is_incognito: true,
|
||||
use_schematized_api: true,
|
||||
skip_search_enabled: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mapHttpError(status: number): SearchError {
|
||||
if (status === 401 || status === 403) {
|
||||
return new SearchError(
|
||||
"AUTH",
|
||||
"Perplexity rejected authentication (401/403). Sign in to Perplexity desktop app and retry.",
|
||||
);
|
||||
}
|
||||
|
||||
if (status === 429) {
|
||||
return new SearchError(
|
||||
"RATE_LIMIT",
|
||||
"Perplexity rate limited this request (429). Wait a bit, then retry.",
|
||||
);
|
||||
}
|
||||
|
||||
return new SearchError(
|
||||
"NETWORK",
|
||||
`Perplexity request failed with HTTP ${status}. Check connectivity and retry.`,
|
||||
);
|
||||
}
|
||||
|
||||
/** Execute a Perplexity search: POST SSE, stream/merge events, extract answer + sources. Throws SearchError on failure. */
|
||||
export async function searchPerplexity(
|
||||
params: SearchParams,
|
||||
jwt: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<SearchResult> {
|
||||
const requestId = crypto.randomUUID();
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(PERPLEXITY_ENDPOINT, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "text/event-stream",
|
||||
Origin: "https://www.perplexity.ai",
|
||||
Referer: "https://www.perplexity.ai/",
|
||||
"User-Agent": PERPLEXITY_USER_AGENT,
|
||||
"X-App-ApiClient": "default",
|
||||
"X-App-ApiVersion": "2.18",
|
||||
"X-Perplexity-Request-Reason": "submit",
|
||||
"X-Request-ID": requestId,
|
||||
},
|
||||
body: JSON.stringify(buildRequestBody(params)),
|
||||
signal,
|
||||
});
|
||||
} catch (error) {
|
||||
if (signal?.aborted) {
|
||||
throw new SearchError("NETWORK", "Perplexity request was cancelled.");
|
||||
}
|
||||
|
||||
throw new SearchError(
|
||||
"NETWORK",
|
||||
`Could not connect to Perplexity. ${(error as Error).message || "Network failure."}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw mapHttpError(response.status);
|
||||
}
|
||||
|
||||
if (!response.body) {
|
||||
throw new SearchError("STREAM", "Perplexity returned an empty stream body.");
|
||||
}
|
||||
|
||||
let snapshot: StreamEvent = {};
|
||||
|
||||
try {
|
||||
for await (const event of readSseEvents(response.body, signal)) {
|
||||
snapshot = mergeEvent(snapshot, event);
|
||||
if (event.final || event.status === "COMPLETED") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof SearchError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (signal?.aborted) {
|
||||
throw new SearchError("NETWORK", "Perplexity request was cancelled.");
|
||||
}
|
||||
|
||||
throw new SearchError(
|
||||
"STREAM",
|
||||
`Failed to parse Perplexity stream: ${(error as Error).message || "unknown error"}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (snapshot.error_code || snapshot.error_message) {
|
||||
throw new SearchError(
|
||||
"STREAM",
|
||||
snapshot.error_message || `Perplexity stream error: ${snapshot.error_code}`,
|
||||
);
|
||||
}
|
||||
|
||||
const answer = extractAnswer(snapshot);
|
||||
const sources = extractSources(snapshot);
|
||||
|
||||
if (!answer && sources.length === 0) {
|
||||
throw new SearchError(
|
||||
"EMPTY",
|
||||
"Perplexity returned no answer and no sources for this query.",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
answer: answer || "No answer text returned by Perplexity.",
|
||||
sources,
|
||||
displayModel: snapshot.display_model,
|
||||
uuid: snapshot.uuid,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { SearchResult, WebResult } from "./types.js";
|
||||
|
||||
const MAX_SNIPPET_LENGTH = 240;
|
||||
|
||||
function truncateSnippet(snippet: string): string {
|
||||
const normalized = snippet.replace(/\s+/g, " ").trim();
|
||||
if (normalized.length <= MAX_SNIPPET_LENGTH) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return `${normalized.slice(0, MAX_SNIPPET_LENGTH - 3)}...`;
|
||||
}
|
||||
|
||||
function humanizeAge(timestamp?: string): string {
|
||||
if (!timestamp) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const parsed = Date.parse(timestamp);
|
||||
if (Number.isNaN(parsed)) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
const diffMs = Math.max(0, Date.now() - parsed);
|
||||
const diffSeconds = Math.floor(diffMs / 1000);
|
||||
|
||||
if (diffSeconds < 60) {
|
||||
return "just now";
|
||||
}
|
||||
|
||||
const diffMinutes = Math.floor(diffSeconds / 60);
|
||||
if (diffMinutes < 60) {
|
||||
return `${diffMinutes}m ago`;
|
||||
}
|
||||
|
||||
const diffHours = Math.floor(diffMinutes / 60);
|
||||
if (diffHours < 24) {
|
||||
return `${diffHours}h ago`;
|
||||
}
|
||||
|
||||
const diffDays = Math.floor(diffHours / 24);
|
||||
return `${diffDays}d ago`;
|
||||
}
|
||||
|
||||
function formatSource(source: WebResult, index: number): string {
|
||||
const title = source.name?.trim() || "Untitled source";
|
||||
const age = humanizeAge(source.timestamp);
|
||||
const lines: string[] = [`[${index + 1}] ${title} (${age})`];
|
||||
|
||||
if (source.url?.trim()) {
|
||||
lines.push(` ${source.url.trim()}`);
|
||||
}
|
||||
|
||||
if (source.snippet?.trim()) {
|
||||
lines.push(` ${truncateSnippet(source.snippet)}`);
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** Format a SearchResult into LLM-friendly text with ## Answer, ## Sources, ## Meta sections. */
|
||||
export function formatForLLM(result: SearchResult, limit?: number): string {
|
||||
const sourceLimit =
|
||||
typeof limit === "number" && Number.isFinite(limit)
|
||||
? Math.max(0, Math.floor(limit))
|
||||
: result.sources.length;
|
||||
|
||||
const limitedSources = result.sources.slice(0, sourceLimit);
|
||||
|
||||
const sourceSection =
|
||||
limitedSources.length === 0
|
||||
? "0 sources\n(no sources returned)"
|
||||
: `${limitedSources.length} sources\n${limitedSources
|
||||
.map((source, index) => formatSource(source, index))
|
||||
.join("\n\n")}`;
|
||||
|
||||
const metaLines = [
|
||||
"Provider: perplexity (oauth)",
|
||||
`Model: ${result.displayModel ?? "unknown"}`,
|
||||
];
|
||||
|
||||
if (result.uuid) {
|
||||
metaLines.push(`Request ID: ${result.uuid}`);
|
||||
}
|
||||
|
||||
return [
|
||||
"## Answer",
|
||||
result.answer.trim() || "No answer returned.",
|
||||
"",
|
||||
"## Sources",
|
||||
sourceSection,
|
||||
"",
|
||||
"## Meta",
|
||||
...metaLines,
|
||||
].join("\n");
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import type { StreamBlock, StreamEvent } from "./types.js";
|
||||
|
||||
function parseEventPayload(payload: string): StreamEvent | null {
|
||||
const trimmed = payload.trim();
|
||||
if (!trimmed || trimmed === "[DONE]") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as unknown;
|
||||
if (!parsed || typeof parsed !== "object") {
|
||||
return null;
|
||||
}
|
||||
return parsed as StreamEvent;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse SSE `data:` lines from a ReadableStream, yielding parsed StreamEvent objects. Handles multi-line payloads, `[DONE]` marker, and abort signal. */
|
||||
export async function* readSseEvents(
|
||||
body: ReadableStream<Uint8Array>,
|
||||
signal?: AbortSignal,
|
||||
): AsyncGenerator<StreamEvent> {
|
||||
const reader = body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
let bufferedText = "";
|
||||
let dataLines: string[] = [];
|
||||
|
||||
const flushEvent = (): StreamEvent | null | "done" => {
|
||||
if (dataLines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const payload = dataLines.join("\n");
|
||||
dataLines = [];
|
||||
|
||||
if (payload.trim() === "[DONE]") {
|
||||
return "done";
|
||||
}
|
||||
|
||||
return parseEventPayload(payload);
|
||||
};
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
if (signal?.aborted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
bufferedText += decoder.decode(value, { stream: true });
|
||||
|
||||
while (true) {
|
||||
const newlineIndex = bufferedText.indexOf("\n");
|
||||
if (newlineIndex < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const rawLine = bufferedText.slice(0, newlineIndex);
|
||||
bufferedText = bufferedText.slice(newlineIndex + 1);
|
||||
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
||||
|
||||
if (line === "") {
|
||||
const parsed = flushEvent();
|
||||
if (parsed === "done") {
|
||||
return;
|
||||
}
|
||||
if (parsed) {
|
||||
yield parsed;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith("data:")) {
|
||||
dataLines.push(line.slice(5).trimStart());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufferedText += decoder.decode();
|
||||
const tail = bufferedText.trim();
|
||||
if (tail.startsWith("data:")) {
|
||||
dataLines.push(tail.slice(5).trimStart());
|
||||
}
|
||||
|
||||
const parsed = flushEvent();
|
||||
if (parsed && parsed !== "done") {
|
||||
yield parsed;
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
/** Merge a markdown block's chunks, respecting chunk_starting_offset for splice. */
|
||||
export function mergeMarkdownBlock(
|
||||
existing: { answer?: string; chunks?: string[]; chunk_starting_offset?: number },
|
||||
incoming: { answer?: string; chunks?: string[]; chunk_starting_offset?: number },
|
||||
): { answer?: string; chunks?: string[]; chunk_starting_offset?: number } {
|
||||
const currentChunks = existing.chunks ?? [];
|
||||
let mergedChunks = currentChunks;
|
||||
|
||||
if (incoming.chunks) {
|
||||
if (typeof incoming.chunk_starting_offset === "number") {
|
||||
if (incoming.chunk_starting_offset <= 0) {
|
||||
mergedChunks = [...incoming.chunks];
|
||||
} else {
|
||||
mergedChunks = [
|
||||
...currentChunks.slice(0, incoming.chunk_starting_offset),
|
||||
...incoming.chunks,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
mergedChunks = [...incoming.chunks];
|
||||
}
|
||||
}
|
||||
|
||||
const mergedAnswer =
|
||||
incoming.answer ??
|
||||
(mergedChunks.length > 0 ? mergedChunks.join("") : undefined) ??
|
||||
existing.answer;
|
||||
|
||||
return {
|
||||
...existing,
|
||||
...incoming,
|
||||
answer: mergedAnswer,
|
||||
chunks: mergedChunks,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeSingleBlock(existing: StreamBlock, incoming: StreamBlock): StreamBlock {
|
||||
const merged: StreamBlock = {
|
||||
...existing,
|
||||
...incoming,
|
||||
};
|
||||
|
||||
if (existing.markdown_block || incoming.markdown_block) {
|
||||
merged.markdown_block = mergeMarkdownBlock(
|
||||
existing.markdown_block ?? {},
|
||||
incoming.markdown_block ?? {},
|
||||
);
|
||||
}
|
||||
|
||||
if (existing.web_result_block || incoming.web_result_block) {
|
||||
merged.web_result_block = {
|
||||
web_results:
|
||||
incoming.web_result_block?.web_results ?? existing.web_result_block?.web_results ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/** Merge block arrays keyed by intended_usage. */
|
||||
export function mergeBlocks(
|
||||
existing: StreamBlock[],
|
||||
incoming: StreamBlock[],
|
||||
): StreamBlock[] {
|
||||
const result = existing.map((block) => ({ ...block }));
|
||||
|
||||
for (const incomingBlock of incoming) {
|
||||
if (!incomingBlock.intended_usage) {
|
||||
result.push({ ...incomingBlock });
|
||||
continue;
|
||||
}
|
||||
|
||||
const index = result.findIndex(
|
||||
(existingBlock) => existingBlock.intended_usage === incomingBlock.intended_usage,
|
||||
);
|
||||
|
||||
if (index < 0) {
|
||||
result.push({ ...incomingBlock });
|
||||
continue;
|
||||
}
|
||||
|
||||
result[index] = mergeSingleBlock(result[index], incomingBlock);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Merge two StreamEvents into a single accumulated snapshot. Sources are accumulated, never replaced. */
|
||||
export function mergeEvent(
|
||||
existing: StreamEvent,
|
||||
incoming: StreamEvent,
|
||||
): StreamEvent {
|
||||
const merged: StreamEvent = {
|
||||
...existing,
|
||||
...incoming,
|
||||
};
|
||||
|
||||
if (existing.blocks || incoming.blocks) {
|
||||
merged.blocks = mergeBlocks(existing.blocks ?? [], incoming.blocks ?? []);
|
||||
}
|
||||
|
||||
const existingSources = existing.sources_list ?? [];
|
||||
const incomingSources = incoming.sources_list ?? [];
|
||||
|
||||
if (existingSources.length > 0 || incomingSources.length > 0) {
|
||||
merged.sources_list = [...existingSources, ...incomingSources];
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// --- SSE event types (all fields optional per AGENTS.md: API is unstable) ---
|
||||
|
||||
export interface StreamEvent {
|
||||
status?: string;
|
||||
final?: boolean;
|
||||
text?: string;
|
||||
blocks?: StreamBlock[];
|
||||
sources_list?: StreamSource[];
|
||||
display_model?: string;
|
||||
uuid?: string;
|
||||
error_code?: string;
|
||||
error_message?: string;
|
||||
}
|
||||
|
||||
export interface StreamBlock {
|
||||
intended_usage?: string;
|
||||
markdown_block?: {
|
||||
answer?: string;
|
||||
chunks?: string[];
|
||||
chunk_starting_offset?: number;
|
||||
};
|
||||
web_result_block?: {
|
||||
web_results?: WebResult[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface WebResult {
|
||||
name?: string;
|
||||
url?: string;
|
||||
snippet?: string;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export interface StreamSource {
|
||||
title?: string;
|
||||
url?: string;
|
||||
snippet?: string;
|
||||
date?: string;
|
||||
}
|
||||
|
||||
// --- Auth types ---
|
||||
|
||||
export interface StoredToken {
|
||||
type: "oauth";
|
||||
access: string;
|
||||
expires: number;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
// --- Search result (output of client, input to formatter) ---
|
||||
|
||||
export interface SearchResult {
|
||||
answer: string;
|
||||
sources: WebResult[];
|
||||
displayModel?: string;
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
// --- Error types ---
|
||||
|
||||
export type SearchErrorCode = "AUTH" | "RATE_LIMIT" | "NETWORK" | "STREAM" | "EMPTY";
|
||||
|
||||
export class SearchError extends Error {
|
||||
constructor(
|
||||
public readonly code: SearchErrorCode,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "SearchError";
|
||||
}
|
||||
}
|
||||
|
||||
export type AuthErrorCode = "NO_TOKEN" | "EXPIRED" | "EXTRACTION_FAILED";
|
||||
|
||||
export class AuthError extends Error {
|
||||
constructor(
|
||||
public readonly code: AuthErrorCode,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "AuthError";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
|
||||
import { decodeJwtExpiry, isJwtExpired } from "../../src/auth/jwt.js";
|
||||
|
||||
const FIXED_NOW = Date.UTC(2026, 1, 16, 12, 0, 0);
|
||||
|
||||
function createJwt(expSeconds: number): string {
|
||||
const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString("base64url");
|
||||
const payload = Buffer.from(JSON.stringify({ exp: expSeconds })).toString("base64url");
|
||||
return `${header}.${payload}.signature`;
|
||||
}
|
||||
|
||||
describe("jwt helpers", () => {
|
||||
const originalNow = Date.now;
|
||||
|
||||
beforeEach(() => {
|
||||
Date.now = () => FIXED_NOW;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Date.now = originalNow;
|
||||
});
|
||||
|
||||
test("decodeJwtExpiry returns expiry in ms with 5 minute safety margin", () => {
|
||||
const expSeconds = Math.floor((FIXED_NOW + 2 * 60 * 60 * 1000) / 1000);
|
||||
const token = createJwt(expSeconds);
|
||||
|
||||
expect(decodeJwtExpiry(token)).toBe(expSeconds * 1000 - 5 * 60 * 1000);
|
||||
});
|
||||
|
||||
test("decodeJwtExpiry falls back to now + 1h when token is malformed", () => {
|
||||
expect(decodeJwtExpiry("not-a-jwt")).toBe(FIXED_NOW + 60 * 60 * 1000);
|
||||
});
|
||||
|
||||
test("isJwtExpired honors additional caller-provided buffer", () => {
|
||||
const expSeconds = Math.floor((FIXED_NOW + 20 * 60 * 1000) / 1000);
|
||||
const token = createJwt(expSeconds);
|
||||
|
||||
expect(isJwtExpired(token)).toBe(false);
|
||||
expect(isJwtExpired(token, 16 * 60 * 1000)).toBe(true);
|
||||
});
|
||||
});
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
data: {"status":"IN_PROGRESS",
|
||||
data: "text":"partial"}
|
||||
|
||||
data: {"status":"COMPLETED","final":true,"text":"final answer","display_model":"pplx_pro_upgraded","uuid":"req-basic","sources_list":[{"title":"Example","url":"https://example.com","snippet":"Example source","date":"2026-02-15T12:00:00.000Z"}]}
|
||||
|
||||
data: [DONE]
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
data: {"status":"IN_PROGRESS","blocks":[{"intended_usage":"markdown_block","markdown_block":{"chunks":["Hello ","wor"],"chunk_starting_offset":0}}]}
|
||||
|
||||
data: {"status":"IN_PROGRESS","blocks":[{"intended_usage":"markdown_block","markdown_block":{"chunks":["world"],"chunk_starting_offset":1}}],"sources_list":[{"title":"Source A","url":"https://a.example","snippet":"A","date":"2026-02-15T10:00:00.000Z"}]}
|
||||
|
||||
data: {"status":"COMPLETED","final":true,"blocks":[{"intended_usage":"web_results","web_result_block":{"web_results":[{"name":"Source B","url":"https://b.example","snippet":"B","timestamp":"2026-02-14T10:00:00.000Z"}]}}],"display_model":"pplx_pro_upgraded","uuid":"req-incremental"}
|
||||
|
||||
data: [DONE]
|
||||
@@ -0,0 +1,241 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test";
|
||||
|
||||
import { searchPerplexity } from "../../src/search/client.js";
|
||||
import { SearchError } from "../../src/search/types.js";
|
||||
|
||||
const ENDPOINT = "https://www.perplexity.ai/rest/sse/perplexity_ask";
|
||||
|
||||
function createSseResponse(events: Array<Record<string, unknown>>, status = 200): Response {
|
||||
const streamText = [
|
||||
...events.map((event) => `data: ${JSON.stringify(event)}\n\n`),
|
||||
"data: [DONE]\n\n",
|
||||
].join("");
|
||||
|
||||
return new Response(streamText, {
|
||||
status,
|
||||
headers: {
|
||||
"content-type": "text/event-stream",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe("searchPerplexity", () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
});
|
||||
|
||||
test("builds request body and headers according to protocol", async () => {
|
||||
let capturedUrl: RequestInfo | URL | undefined;
|
||||
let capturedInit: RequestInit | undefined;
|
||||
|
||||
globalThis.fetch = (async (url: RequestInfo | URL, init?: RequestInit) => {
|
||||
capturedUrl = url;
|
||||
capturedInit = init;
|
||||
|
||||
return createSseResponse([
|
||||
{
|
||||
status: "COMPLETED",
|
||||
final: true,
|
||||
blocks: [
|
||||
{
|
||||
intended_usage: "markdown_block",
|
||||
markdown_block: {
|
||||
answer: "answer text",
|
||||
},
|
||||
},
|
||||
{
|
||||
intended_usage: "web_results",
|
||||
web_result_block: {
|
||||
web_results: [
|
||||
{
|
||||
name: "Source",
|
||||
url: "https://example.com",
|
||||
snippet: "snippet",
|
||||
timestamp: "2026-02-16T10:00:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}) as unknown as typeof fetch;
|
||||
|
||||
const controller = new AbortController();
|
||||
const result = await searchPerplexity(
|
||||
{ query: "latest bun release notes", recency: "week" },
|
||||
"jwt-token",
|
||||
controller.signal,
|
||||
);
|
||||
|
||||
expect(String(capturedUrl)).toBe(ENDPOINT);
|
||||
expect(capturedInit?.method).toBe("POST");
|
||||
expect(capturedInit?.signal).toBe(controller.signal);
|
||||
|
||||
const headers = new Headers(capturedInit?.headers);
|
||||
expect(headers.get("Authorization")).toBe("Bearer jwt-token");
|
||||
expect(headers.get("Accept")).toBe("text/event-stream");
|
||||
expect(headers.get("X-App-ApiVersion")).toBe("2.18");
|
||||
expect(headers.get("X-Request-ID")).toBeTruthy();
|
||||
|
||||
const body = JSON.parse(String(capturedInit?.body)) as {
|
||||
query_str: string;
|
||||
params: {
|
||||
query_str: string;
|
||||
mode: string;
|
||||
model_preference: string;
|
||||
is_incognito: boolean;
|
||||
search_recency_filter: string | null;
|
||||
frontend_uuid: string;
|
||||
frontend_context_uuid: string;
|
||||
};
|
||||
};
|
||||
|
||||
expect(body.query_str).toBe("latest bun release notes");
|
||||
expect(body.params.query_str).toBe("latest bun release notes");
|
||||
expect(body.params.mode).toBe("copilot");
|
||||
expect(body.params.model_preference).toBe("pplx_pro_upgraded");
|
||||
expect(body.params.is_incognito).toBe(true);
|
||||
expect(body.params.search_recency_filter).toBe("week");
|
||||
expect(body.params.frontend_uuid).toBeTruthy();
|
||||
expect(body.params.frontend_context_uuid).toBeTruthy();
|
||||
|
||||
expect(result.answer).toBe("answer text");
|
||||
expect(result.sources).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("maps 401 and 403 responses to AUTH error", async () => {
|
||||
for (const status of [401, 403]) {
|
||||
globalThis.fetch = (async () => new Response("auth fail", { status })) as unknown as typeof fetch;
|
||||
|
||||
await expect(searchPerplexity({ query: "q" }, "jwt")).rejects.toMatchObject({
|
||||
name: "SearchError",
|
||||
code: "AUTH",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("maps 429 responses to RATE_LIMIT error", async () => {
|
||||
globalThis.fetch = (async () => new Response("rate limited", { status: 429 })) as unknown as typeof fetch;
|
||||
|
||||
await expect(searchPerplexity({ query: "q" }, "jwt")).rejects.toMatchObject({
|
||||
name: "SearchError",
|
||||
code: "RATE_LIMIT",
|
||||
});
|
||||
});
|
||||
|
||||
test("deduplicates sources by normalized URL", async () => {
|
||||
globalThis.fetch = (async () =>
|
||||
createSseResponse([
|
||||
{
|
||||
status: "COMPLETED",
|
||||
final: true,
|
||||
blocks: [
|
||||
{
|
||||
intended_usage: "markdown_block",
|
||||
markdown_block: {
|
||||
answer: "answer text",
|
||||
},
|
||||
},
|
||||
{
|
||||
intended_usage: "web_results",
|
||||
web_result_block: {
|
||||
web_results: [
|
||||
{ name: "A", url: "https://example.com/path" },
|
||||
{ name: "A duplicate", url: "https://example.com/path/" },
|
||||
{ name: "B", url: "https://another.example/path" },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])) as unknown as typeof fetch;
|
||||
|
||||
const result = await searchPerplexity({ query: "q" }, "jwt");
|
||||
|
||||
expect(result.sources).toHaveLength(2);
|
||||
expect(result.sources[0].url).toBe("https://example.com/path");
|
||||
expect(result.sources[1].url).toBe("https://another.example/path");
|
||||
});
|
||||
|
||||
test("answer extraction prioritizes markdown_block over ask_text and text", async () => {
|
||||
globalThis.fetch = (async () =>
|
||||
createSseResponse([
|
||||
{
|
||||
status: "COMPLETED",
|
||||
final: true,
|
||||
text: "fallback text",
|
||||
blocks: [
|
||||
{
|
||||
intended_usage: "ask_text",
|
||||
markdown_block: { answer: "ask text" },
|
||||
},
|
||||
{
|
||||
intended_usage: "markdown_block",
|
||||
markdown_block: { answer: "markdown answer" },
|
||||
},
|
||||
],
|
||||
sources_list: [{ title: "S", url: "https://example.com" }],
|
||||
},
|
||||
])) as unknown as typeof fetch;
|
||||
|
||||
const result = await searchPerplexity({ query: "q" }, "jwt");
|
||||
expect(result.answer).toBe("markdown answer");
|
||||
});
|
||||
|
||||
test("answer extraction falls back to ask_text then text", async () => {
|
||||
globalThis.fetch = (async () =>
|
||||
createSseResponse([
|
||||
{
|
||||
status: "COMPLETED",
|
||||
final: true,
|
||||
text: "fallback text",
|
||||
blocks: [
|
||||
{
|
||||
intended_usage: "ask_text",
|
||||
markdown_block: { answer: "ask answer" },
|
||||
},
|
||||
],
|
||||
sources_list: [{ title: "S", url: "https://example.com" }],
|
||||
},
|
||||
])) as unknown as typeof fetch;
|
||||
|
||||
const askTextResult = await searchPerplexity({ query: "q" }, "jwt");
|
||||
expect(askTextResult.answer).toBe("ask answer");
|
||||
|
||||
globalThis.fetch = (async () =>
|
||||
createSseResponse([
|
||||
{
|
||||
status: "COMPLETED",
|
||||
final: true,
|
||||
text: "text fallback",
|
||||
sources_list: [{ title: "S", url: "https://example.com" }],
|
||||
},
|
||||
])) as unknown as typeof fetch;
|
||||
|
||||
const textResult = await searchPerplexity({ query: "q" }, "jwt");
|
||||
expect(textResult.answer).toBe("text fallback");
|
||||
});
|
||||
|
||||
test("returns EMPTY error when response has no answer and no sources", async () => {
|
||||
globalThis.fetch = (async () =>
|
||||
createSseResponse([
|
||||
{
|
||||
status: "COMPLETED",
|
||||
final: true,
|
||||
},
|
||||
])) as unknown as typeof fetch;
|
||||
|
||||
let thrown: unknown;
|
||||
try {
|
||||
await searchPerplexity({ query: "q" }, "jwt");
|
||||
} catch (error) {
|
||||
thrown = error;
|
||||
}
|
||||
|
||||
expect(thrown).toBeInstanceOf(SearchError);
|
||||
expect((thrown as SearchError).code).toBe("EMPTY");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
||||
|
||||
import { formatForLLM } from "../../src/search/format.js";
|
||||
|
||||
const NOW = Date.UTC(2026, 1, 16, 12, 0, 0);
|
||||
|
||||
describe("formatForLLM", () => {
|
||||
const originalNow = Date.now;
|
||||
|
||||
beforeEach(() => {
|
||||
Date.now = () => NOW;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Date.now = originalNow;
|
||||
});
|
||||
|
||||
test("renders required sections and deterministic source ordering", () => {
|
||||
const output = formatForLLM({
|
||||
answer: "Answer body",
|
||||
sources: [
|
||||
{
|
||||
name: "Source 1",
|
||||
url: "https://example.com/1",
|
||||
snippet: "Snippet 1",
|
||||
timestamp: new Date(NOW - 3 * 60 * 60 * 1000).toISOString(),
|
||||
},
|
||||
{
|
||||
name: "Source 2",
|
||||
url: "https://example.com/2",
|
||||
snippet: "Snippet 2",
|
||||
timestamp: new Date(NOW - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
},
|
||||
],
|
||||
displayModel: "pplx_pro_upgraded",
|
||||
uuid: "req-123",
|
||||
});
|
||||
|
||||
expect(output).toContain("## Answer");
|
||||
expect(output).toContain("## Sources");
|
||||
expect(output).toContain("## Meta");
|
||||
expect(output.indexOf("[1] Source 1")).toBeLessThan(output.indexOf("[2] Source 2"));
|
||||
expect(output).toContain("Provider: perplexity (oauth)");
|
||||
expect(output).toContain("Model: pplx_pro_upgraded");
|
||||
expect(output).toContain("Request ID: req-123");
|
||||
});
|
||||
|
||||
test("humanizes source ages", () => {
|
||||
const output = formatForLLM({
|
||||
answer: "Age test",
|
||||
sources: [
|
||||
{
|
||||
name: "Recent",
|
||||
url: "https://example.com/recent",
|
||||
snippet: "recent snippet",
|
||||
timestamp: new Date(NOW - 30 * 1000).toISOString(),
|
||||
},
|
||||
{
|
||||
name: "Minutes",
|
||||
url: "https://example.com/minutes",
|
||||
snippet: "minutes snippet",
|
||||
timestamp: new Date(NOW - 12 * 60 * 1000).toISOString(),
|
||||
},
|
||||
{
|
||||
name: "Hours",
|
||||
url: "https://example.com/hours",
|
||||
snippet: "hours snippet",
|
||||
timestamp: new Date(NOW - 5 * 60 * 60 * 1000).toISOString(),
|
||||
},
|
||||
{
|
||||
name: "Days",
|
||||
url: "https://example.com/days",
|
||||
snippet: "days snippet",
|
||||
timestamp: new Date(NOW - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(output).toContain("Recent (just now)");
|
||||
expect(output).toContain("Minutes (12m ago)");
|
||||
expect(output).toContain("Hours (5h ago)");
|
||||
expect(output).toContain("Days (3d ago)");
|
||||
});
|
||||
|
||||
test("truncates snippets to 240 chars", () => {
|
||||
const longSnippet = "x".repeat(300);
|
||||
|
||||
const output = formatForLLM({
|
||||
answer: "Snippet test",
|
||||
sources: [
|
||||
{
|
||||
name: "Long snippet",
|
||||
url: "https://example.com/long",
|
||||
snippet: longSnippet,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const snippetLine = output
|
||||
.split("\n")
|
||||
.find((line) => line.startsWith(" ") && line.includes("..."));
|
||||
|
||||
expect(snippetLine).toBeDefined();
|
||||
expect(snippetLine!.trim().length).toBe(240);
|
||||
});
|
||||
|
||||
test("handles empty source list", () => {
|
||||
const output = formatForLLM({
|
||||
answer: "No sources",
|
||||
sources: [],
|
||||
});
|
||||
|
||||
expect(output).toContain("0 sources");
|
||||
expect(output).toContain("(no sources returned)");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { mergeEvent, mergeMarkdownBlock, readSseEvents } from "../../src/search/stream.js";
|
||||
import type { StreamEvent } from "../../src/search/types.js";
|
||||
|
||||
function streamFromString(input: string, chunkSize = 8): ReadableStream<Uint8Array> {
|
||||
const encoded = new TextEncoder().encode(input);
|
||||
|
||||
return new ReadableStream<Uint8Array>({
|
||||
start(controller) {
|
||||
for (let index = 0; index < encoded.length; index += chunkSize) {
|
||||
controller.enqueue(encoded.slice(index, index + chunkSize));
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function collectEvents(stream: ReadableStream<Uint8Array>): Promise<StreamEvent[]> {
|
||||
const events: StreamEvent[] = [];
|
||||
for await (const event of readSseEvents(stream)) {
|
||||
events.push(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
describe("SSE stream parsing", () => {
|
||||
test("parses multiline data payloads and stops at [DONE]", async () => {
|
||||
const fixture = await Bun.file("test/fixtures/sse-basic.txt").text();
|
||||
const events = await collectEvents(streamFromString(fixture, 5));
|
||||
|
||||
expect(events).toHaveLength(2);
|
||||
expect(events[0].status).toBe("IN_PROGRESS");
|
||||
expect(events[0].text).toBe("partial");
|
||||
expect(events[1].status).toBe("COMPLETED");
|
||||
expect(events[1].final).toBe(true);
|
||||
});
|
||||
|
||||
test("skips invalid JSON payloads and continues parsing", async () => {
|
||||
const payload = [
|
||||
"data: {invalid-json}",
|
||||
"",
|
||||
'data: {"status":"COMPLETED","text":"ok"}',
|
||||
"",
|
||||
"data: [DONE]",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
const events = await collectEvents(streamFromString(payload));
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].text).toBe("ok");
|
||||
});
|
||||
});
|
||||
|
||||
describe("event merging", () => {
|
||||
test("mergeMarkdownBlock splices chunks at chunk_starting_offset", () => {
|
||||
const merged = mergeMarkdownBlock(
|
||||
{
|
||||
chunks: ["Hello ", "wor"],
|
||||
chunk_starting_offset: 0,
|
||||
},
|
||||
{
|
||||
chunks: ["world"],
|
||||
chunk_starting_offset: 1,
|
||||
},
|
||||
);
|
||||
|
||||
expect(merged.chunks).toEqual(["Hello ", "world"]);
|
||||
expect(merged.answer).toBe("Hello world");
|
||||
});
|
||||
|
||||
test("mergeEvent preserves and accumulates sources_list", () => {
|
||||
const first = mergeEvent(
|
||||
{ sources_list: [{ title: "A", url: "https://a.example" }] },
|
||||
{ text: "step 1" },
|
||||
);
|
||||
|
||||
expect(first.sources_list).toEqual([{ title: "A", url: "https://a.example" }]);
|
||||
|
||||
const second = mergeEvent(first, {
|
||||
sources_list: [{ title: "B", url: "https://b.example" }],
|
||||
status: "COMPLETED",
|
||||
});
|
||||
|
||||
expect(second.sources_list).toEqual([
|
||||
{ title: "A", url: "https://a.example" },
|
||||
{ title: "B", url: "https://b.example" },
|
||||
]);
|
||||
});
|
||||
|
||||
test("incremental fixture merges markdown and metadata", async () => {
|
||||
const fixture = await Bun.file("test/fixtures/sse-incremental.txt").text();
|
||||
let snapshot: StreamEvent = {};
|
||||
|
||||
for await (const event of readSseEvents(streamFromString(fixture, 11))) {
|
||||
snapshot = mergeEvent(snapshot, event);
|
||||
}
|
||||
|
||||
const markdown = snapshot.blocks?.find((block) => block.intended_usage === "markdown_block")
|
||||
?.markdown_block;
|
||||
|
||||
expect(markdown?.chunks).toEqual(["Hello ", "world"]);
|
||||
expect(markdown?.answer).toBe("Hello world");
|
||||
expect(snapshot.display_model).toBe("pplx_pro_upgraded");
|
||||
expect(snapshot.uuid).toBe("req-incremental");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"types": ["bun-types"]
|
||||
},
|
||||
"include": ["src", "test"]
|
||||
}
|
||||
Reference in New Issue
Block a user