@a-dray/aglib 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/model/adapters/openai-compatible/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ContentPart, Message, Model, ModelDelta, ModelError, ModelRequest, ModelResponse, ToolCall, ToolSpec, Usage,\n} from \"../../model.js\";\nimport { err, ok, type Result } from \"../../../result.js\";\nimport { textOf } from \"../../../content.js\";\n\nexport interface OpenAiCompatibleOptions {\n apiKey: string;\n baseUrl: string;\n model: string;\n headers?: Readonly<Record<string, string>>;\n /**\n * How this endpoint spells reasoning effort. OpenAI takes `reasoning_effort`;\n * OpenRouter takes `reasoning: { effort }` and forwards it to whoever is\n * serving the model. Sending both is not compatibility — OpenAI rejects the\n * request outright — so the dialect is declared rather than guessed.\n */\n effortParameter?: \"reasoning_effort\" | \"reasoning\" | \"none\";\n /** Injectable for tests; defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\n/**\n * The OpenAI chat-completions wire, which almost every provider now speaks —\n * OpenRouter, Together, Groq, vLLM, Ollama. One adapter, one base URL.\n */\nexport function createOpenAiCompatibleModel(options: OpenAiCompatibleOptions): Model {\n const call = options.fetch ?? fetch;\n return {\n id: `openai-compatible:${options.model}`,\n\n async *generate(request: ModelRequest): AsyncGenerator<ModelDelta, Result<ModelResponse, ModelError>> {\n let response: Response;\n try {\n response = await call(`${options.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${options.apiKey}`,\n ...options.headers,\n },\n body: JSON.stringify({\n model: options.model,\n messages: request.messages.map(encodeMessage),\n ...(request.tools?.length ? { tools: request.tools.map(encodeTool) } : {}),\n ...(request.maxOutputTokens !== undefined ? { max_tokens: request.maxOutputTokens } : {}),\n ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),\n ...encodeEffort(request.effort, options.effortParameter ?? \"reasoning_effort\"),\n stream: true,\n stream_options: { include_usage: true },\n }),\n ...(request.signal ? { signal: request.signal } : {}),\n });\n } catch (error) {\n return err(transportError(error, request.signal));\n }\n\n if (!response.ok) return err(await httpError(response));\n if (!response.body) return err({ code: \"provider\", message: \"No response body\", retryable: true });\n\n const text: string[] = [];\n const calls = new Map<number, { callId: string; name: string; arguments: string }>();\n let finish: ModelResponse[\"finishReason\"] = \"stop\";\n let usage: Usage = {};\n let model: string | undefined;\n\n // A stream that dies mid-body — cancelled, dropped, truncated — must come\n // back as a typed failure like any other. Without this the generator throws,\n // `run.result` rejects instead of resolving `cancelled`, no `run.finished`\n // is ever committed, and the activation stays open until its claim expires.\n try {\n for await (const event of sseLines(response.body)) {\n if (event === \"[DONE]\") break;\n let frame: ChatFrame;\n try { frame = JSON.parse(event) as ChatFrame; } catch { continue; }\n model ??= frame.model;\n if (frame.usage) usage = decodeUsage(frame.usage);\n\n const choice = frame.choices?.[0];\n if (!choice) continue;\n if (choice.finish_reason) finish = decodeFinish(choice.finish_reason);\n\n const content = choice.delta?.content;\n if (content) { text.push(content); yield { type: \"text.delta\", text: content }; }\n\n const reasoning = choice.delta?.reasoning;\n if (reasoning) yield { type: \"reasoning.delta\", text: reasoning };\n\n for (const fragment of choice.delta?.tool_calls ?? []) {\n // Providers stream tool arguments in fragments, keyed by position;\n // only the first fragment carries the id and the name.\n const existing = calls.get(fragment.index) ?? {\n callId: fragment.id ?? `call_${fragment.index}`,\n name: fragment.function?.name ?? \"\",\n arguments: \"\",\n };\n if (fragment.id) existing.callId = fragment.id;\n if (fragment.function?.name) existing.name = fragment.function.name;\n if (fragment.function?.arguments) {\n existing.arguments += fragment.function.arguments;\n yield { type: \"tool-call.delta\", callId: existing.callId, arguments: fragment.function.arguments };\n }\n calls.set(fragment.index, existing);\n }\n }\n } catch (error) {\n return err(transportError(error, request.signal));\n }\n\n const collected: ToolCall[] = [...calls.values()]\n .map((call) => ({ callId: call.callId, name: call.name, arguments: call.arguments || \"{}\" }));\n\n return ok({\n message: { content: text.join(\"\"), ...(collected.length ? { calls: collected } : {}) },\n finishReason: collected.length && finish === \"stop\" ? \"tool-calls\" : finish,\n usage,\n ...(model ? { model } : {}),\n });\n },\n };\n}\n\n/** OpenRouter is this wire with a fixed base URL and attribution headers. */\nexport function createOpenRouterModel(input: {\n apiKey: string; model: string; appUrl?: string; appName?: string; fetch?: typeof fetch;\n}): Model {\n return createOpenAiCompatibleModel({\n apiKey: input.apiKey,\n baseUrl: \"https://openrouter.ai/api/v1\",\n model: input.model,\n effortParameter: \"reasoning\",\n headers: {\n ...(input.appUrl ? { \"http-referer\": input.appUrl } : {}),\n ...(input.appName ? { \"x-title\": input.appName } : {}),\n },\n ...(input.fetch ? { fetch: input.fetch } : {}),\n });\n}\n\nfunction encodeEffort(\n effort: ModelRequest[\"effort\"],\n parameter: \"reasoning_effort\" | \"reasoning\" | \"none\",\n): Record<string, unknown> {\n if (!effort || parameter === \"none\") return {};\n return parameter === \"reasoning\" ? { reasoning: { effort } } : { reasoning_effort: effort };\n}\n\nfunction encodeMessage(message: Message): Record<string, unknown> {\n if (message.role === \"tool\") {\n return { role: \"tool\", tool_call_id: message.callId, content: textOf(message.content) };\n }\n if (message.role === \"assistant\") {\n return {\n role: \"assistant\",\n content: textOf(message.content) || null,\n ...(message.calls?.length\n ? {\n tool_calls: message.calls.map((call) => ({\n id: call.callId, type: \"function\",\n function: { name: call.name, arguments: call.arguments },\n })),\n }\n : {}),\n };\n }\n return { role: message.role, content: encodeContent(message.content) };\n}\n\nfunction encodeContent(content: Message[\"content\"]): unknown {\n if (typeof content === \"string\") return content;\n const parts: Record<string, unknown>[] = [];\n for (const part of content as readonly ContentPart[]) {\n if (part.type === \"text\") { parts.push({ type: \"text\", text: part.text }); continue; }\n if (part.type === \"image\") {\n const url = part.source.kind === \"url\" ? part.source.url : `data:${part.mediaType};base64,${part.source.data}`;\n parts.push({ type: \"image_url\", image_url: { url } });\n }\n // Files and opaque blocks have no place on this wire. Dropping them beats\n // stringifying them into something the model would read as prose.\n }\n const only = parts.length === 1 ? parts[0] : undefined;\n return only?.type === \"text\" ? only.text : parts;\n}\n\nconst encodeTool = (tool: ToolSpec) => ({\n type: \"function\",\n function: { name: tool.name, description: tool.description, parameters: tool.parameters },\n});\n\nconst decodeFinish = (reason: string): ModelResponse[\"finishReason\"] =>\n reason === \"tool_calls\" ? \"tool-calls\"\n : reason === \"length\" ? \"length\"\n : reason === \"content_filter\" ? \"refusal\"\n : \"stop\";\n\nconst decodeUsage = (usage: ChatUsage): Usage => ({\n // Carried, not computed. A router picks an upstream provider per request and\n // adds its own margin, so what it charged is a thing only it can say — and\n // the frame carrying it is the one this adapter already reads.\n ...(typeof usage.cost === \"number\" ? { costUsd: usage.cost } : {}),\n // `prompt_tokens` is the whole prompt and `cached_tokens` is counted inside\n // it — \"cached tokens present in the prompt\". `Usage` keeps the three input\n // counts disjoint, so the cached part comes out here rather than every\n // caller having to know which wire produced the number it is holding.\n ...(usage.prompt_tokens !== undefined\n ? { inputTokens: Math.max(usage.prompt_tokens - (usage.prompt_tokens_details?.cached_tokens ?? 0), 0) } : {}),\n ...(usage.completion_tokens !== undefined ? { outputTokens: usage.completion_tokens } : {}),\n ...(usage.prompt_tokens_details?.cached_tokens !== undefined\n ? { cacheReadTokens: usage.prompt_tokens_details.cached_tokens } : {}),\n});\n\nasync function httpError(response: Response): Promise<ModelError> {\n const body = await response.text().catch(() => \"\");\n const code: ModelError[\"code\"] =\n response.status === 401 || response.status === 403 ? \"auth\"\n : response.status === 429 ? \"rate-limit\"\n : response.status === 400 && /context|token/i.test(body) ? \"context-length\"\n : response.status >= 500 ? \"provider\"\n : \"failed\";\n return {\n code,\n message: `${response.status} ${response.statusText}${body ? `: ${body.slice(0, 400)}` : \"\"}`,\n retryable: code === \"rate-limit\" || code === \"provider\",\n };\n}\n\nfunction transportError(error: unknown, signal?: AbortSignal): ModelError {\n if (signal?.aborted) return { code: \"cancelled\", message: \"Generation cancelled\", retryable: false };\n return { code: \"provider\", message: error instanceof Error ? error.message : String(error), retryable: true };\n}\n\n/** Server-sent events, reassembled across chunk boundaries. */\nasync function* sseLines(body: ReadableStream<Uint8Array>): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n let buffer = \"\";\n for await (const chunk of body as unknown as AsyncIterable<Uint8Array>) {\n buffer += decoder.decode(chunk, { stream: true });\n let newline = buffer.indexOf(\"\\n\");\n while (newline >= 0) {\n const line = buffer.slice(0, newline).trim();\n buffer = buffer.slice(newline + 1);\n if (line.startsWith(\"data:\")) yield line.slice(5).trim();\n newline = buffer.indexOf(\"\\n\");\n }\n }\n}\n\ninterface ChatUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n prompt_tokens_details?: { cached_tokens?: number };\n /**\n * What this generation was charged. OpenRouter sets it on every response and\n * calls it credits, whose base currency is US dollars; plain OpenAI and the\n * self-hosted endpoints on this wire do not send it at all, so it is absent\n * rather than zero there.\n */\n cost?: number;\n}\ninterface ChatFrame {\n model?: string;\n usage?: ChatUsage;\n choices?: readonly {\n finish_reason?: string | null;\n delta?: {\n content?: string;\n reasoning?: string;\n tool_calls?: readonly { index: number; id?: string; function?: { name?: string; arguments?: string } }[];\n };\n }[];\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/model/adapters/openai-compatible/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ContentPart, Message, Model, ModelDelta, ModelError, ModelRequest, ModelResponse, ToolCall, ToolSpec, Usage,\n} from \"../../model.js\";\nimport { err, ok, type Result } from \"../../../result.js\";\nimport { textOf } from \"../../../content.js\";\n\nexport interface OpenAiCompatibleOptions {\n apiKey: string;\n baseUrl: string;\n model: string;\n headers?: Readonly<Record<string, string>>;\n /**\n * How this endpoint spells reasoning effort. OpenAI takes `reasoning_effort`;\n * OpenRouter takes `reasoning: { effort }` and forwards it to whoever is\n * serving the model. Sending both is not compatibility — OpenAI rejects the\n * request outright — so the dialect is declared rather than guessed.\n */\n effortParameter?: \"reasoning_effort\" | \"reasoning\" | \"none\";\n /** Injectable for tests; defaults to global fetch. */\n fetch?: typeof fetch;\n}\n\n/**\n * The OpenAI chat-completions wire, which almost every provider now speaks —\n * OpenRouter, Together, Groq, vLLM, Ollama. One adapter, one base URL.\n */\nexport function createOpenAiCompatibleModel(options: OpenAiCompatibleOptions): Model {\n const call = options.fetch ?? fetch;\n return {\n id: `openai-compatible:${options.model}`,\n\n async *generate(request: ModelRequest): AsyncGenerator<ModelDelta, Result<ModelResponse, ModelError>> {\n let response: Response;\n try {\n response = await call(`${options.baseUrl}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${options.apiKey}`,\n ...options.headers,\n },\n body: JSON.stringify({\n model: options.model,\n messages: request.messages.map(encodeMessage),\n ...(request.tools?.length ? { tools: request.tools.map(encodeTool) } : {}),\n ...(request.maxOutputTokens !== undefined ? { max_tokens: request.maxOutputTokens } : {}),\n ...(request.temperature !== undefined ? { temperature: request.temperature } : {}),\n ...encodeEffort(request.effort, options.effortParameter ?? \"reasoning_effort\"),\n stream: true,\n stream_options: { include_usage: true },\n }),\n ...(request.signal ? { signal: request.signal } : {}),\n });\n } catch (error) {\n return err(transportError(error, request.signal));\n }\n\n if (!response.ok) return err(await httpError(response));\n if (!response.body) return err({ code: \"provider\", message: \"No response body\", retryable: true });\n\n const text: string[] = [];\n const calls = new Map<number, { callId: string; name: string; arguments: string }>();\n let finish: ModelResponse[\"finishReason\"] = \"stop\";\n let usage: Usage = {};\n let model: string | undefined;\n\n // A stream that dies mid-body — cancelled, dropped, truncated — must come\n // back as a typed failure like any other. Without this the generator throws,\n // `run.result` rejects instead of resolving `cancelled`, no `run.finished`\n // is ever committed, and the activation stays open until its claim expires.\n try {\n for await (const event of sseLines(response.body)) {\n if (event === \"[DONE]\") break;\n let frame: ChatFrame;\n try { frame = JSON.parse(event) as ChatFrame; } catch { continue; }\n model ??= frame.model;\n if (frame.usage) usage = decodeUsage(frame.usage);\n\n const choice = frame.choices?.[0];\n if (!choice) continue;\n if (choice.finish_reason) finish = decodeFinish(choice.finish_reason);\n\n const content = choice.delta?.content;\n if (content) { text.push(content); yield { type: \"text.delta\", text: content }; }\n\n const reasoning = choice.delta?.reasoning;\n if (reasoning) yield { type: \"reasoning.delta\", text: reasoning };\n\n for (const fragment of choice.delta?.tool_calls ?? []) {\n // Providers stream tool arguments in fragments, keyed by position;\n // only the first fragment carries the id and the name.\n const existing = calls.get(fragment.index) ?? {\n callId: fragment.id ?? `call_${fragment.index}`,\n name: fragment.function?.name ?? \"\",\n arguments: \"\",\n };\n if (fragment.id) existing.callId = fragment.id;\n if (fragment.function?.name) existing.name = fragment.function.name;\n if (fragment.function?.arguments) {\n existing.arguments += fragment.function.arguments;\n yield { type: \"tool-call.delta\", callId: existing.callId, arguments: fragment.function.arguments };\n }\n calls.set(fragment.index, existing);\n }\n }\n } catch (error) {\n return err(transportError(error, request.signal));\n }\n\n const collected: ToolCall[] = [...calls.values()]\n .map((call) => ({ callId: call.callId, name: call.name, arguments: call.arguments || \"{}\" }));\n\n return ok({\n message: { content: text.join(\"\"), ...(collected.length ? { calls: collected } : {}) },\n finishReason: collected.length && finish === \"stop\" ? \"tool-calls\" : finish,\n usage,\n ...(model ? { model } : {}),\n });\n },\n };\n}\n\n/** OpenRouter is this wire with a fixed base URL and attribution headers. */\nexport function createOpenRouterModel(input: {\n apiKey: string; model: string; appUrl?: string; appName?: string; fetch?: typeof fetch;\n}): Model {\n return createOpenAiCompatibleModel({\n apiKey: input.apiKey,\n baseUrl: \"https://openrouter.ai/api/v1\",\n model: input.model,\n effortParameter: \"reasoning\",\n headers: {\n ...(input.appUrl ? { \"http-referer\": input.appUrl } : {}),\n ...(input.appName ? { \"x-title\": input.appName } : {}),\n },\n ...(input.fetch ? { fetch: input.fetch } : {}),\n });\n}\n\nfunction encodeEffort(\n effort: ModelRequest[\"effort\"],\n parameter: \"reasoning_effort\" | \"reasoning\" | \"none\",\n): Record<string, unknown> {\n if (!effort || parameter === \"none\") return {};\n return parameter === \"reasoning\" ? { reasoning: { effort } } : { reasoning_effort: effort };\n}\n\nfunction encodeMessage(message: Message): Record<string, unknown> {\n if (message.role === \"tool\") {\n return { role: \"tool\", tool_call_id: message.callId, content: textOf(message.content) };\n }\n if (message.role === \"assistant\") {\n return {\n role: \"assistant\",\n content: textOf(message.content) || null,\n ...(message.calls?.length\n ? {\n tool_calls: message.calls.map((call) => ({\n id: call.callId, type: \"function\",\n function: { name: call.name, arguments: call.arguments },\n })),\n }\n : {}),\n };\n }\n return { role: message.role, content: encodeContent(message.content) };\n}\n\nfunction encodeContent(content: Message[\"content\"]): unknown {\n if (typeof content === \"string\") return content;\n const parts: Record<string, unknown>[] = [];\n for (const part of content as readonly ContentPart[]) {\n if (part.type === \"text\") { parts.push({ type: \"text\", text: part.text }); continue; }\n if (part.type === \"image\") {\n const url = part.source.kind === \"url\" ? part.source.url : `data:${part.mediaType};base64,${part.source.data}`;\n parts.push({ type: \"image_url\", image_url: { url } });\n }\n // Files and opaque blocks have no place on this wire. Dropping them beats\n // stringifying them into something the model would read as prose.\n }\n const only = parts.length === 1 ? parts[0] : undefined;\n return only?.type === \"text\" ? only.text : parts;\n}\n\nconst encodeTool = (tool: ToolSpec) => ({\n type: \"function\",\n function: { name: tool.name, description: tool.description, parameters: tool.parameters, strict: false },\n});\n\nconst decodeFinish = (reason: string): ModelResponse[\"finishReason\"] =>\n reason === \"tool_calls\" ? \"tool-calls\"\n : reason === \"length\" ? \"length\"\n : reason === \"content_filter\" ? \"refusal\"\n : \"stop\";\n\nconst decodeUsage = (usage: ChatUsage): Usage => ({\n // Carried, not computed. A router picks an upstream provider per request and\n // adds its own margin, so what it charged is a thing only it can say — and\n // the frame carrying it is the one this adapter already reads.\n ...(typeof usage.cost === \"number\" ? { costUsd: usage.cost } : {}),\n // `prompt_tokens` is the whole prompt and `cached_tokens` is counted inside\n // it — \"cached tokens present in the prompt\". `Usage` keeps the three input\n // counts disjoint, so the cached part comes out here rather than every\n // caller having to know which wire produced the number it is holding.\n ...(usage.prompt_tokens !== undefined\n ? { inputTokens: Math.max(usage.prompt_tokens - (usage.prompt_tokens_details?.cached_tokens ?? 0), 0) } : {}),\n ...(usage.completion_tokens !== undefined ? { outputTokens: usage.completion_tokens } : {}),\n ...(usage.prompt_tokens_details?.cached_tokens !== undefined\n ? { cacheReadTokens: usage.prompt_tokens_details.cached_tokens } : {}),\n});\n\nasync function httpError(response: Response): Promise<ModelError> {\n const body = await response.text().catch(() => \"\");\n const code: ModelError[\"code\"] =\n response.status === 401 || response.status === 403 ? \"auth\"\n : response.status === 429 ? \"rate-limit\"\n : response.status === 400 && /context|token/i.test(body) ? \"context-length\"\n : response.status >= 500 ? \"provider\"\n : \"failed\";\n return {\n code,\n message: `${response.status} ${response.statusText}${body ? `: ${body.slice(0, 400)}` : \"\"}`,\n retryable: code === \"rate-limit\" || code === \"provider\",\n };\n}\n\nfunction transportError(error: unknown, signal?: AbortSignal): ModelError {\n if (signal?.aborted) return { code: \"cancelled\", message: \"Generation cancelled\", retryable: false };\n return { code: \"provider\", message: error instanceof Error ? error.message : String(error), retryable: true };\n}\n\n/** Server-sent events, reassembled across chunk boundaries. */\nasync function* sseLines(body: ReadableStream<Uint8Array>): AsyncGenerator<string> {\n const decoder = new TextDecoder();\n let buffer = \"\";\n for await (const chunk of body as unknown as AsyncIterable<Uint8Array>) {\n buffer += decoder.decode(chunk, { stream: true });\n let newline = buffer.indexOf(\"\\n\");\n while (newline >= 0) {\n const line = buffer.slice(0, newline).trim();\n buffer = buffer.slice(newline + 1);\n if (line.startsWith(\"data:\")) yield line.slice(5).trim();\n newline = buffer.indexOf(\"\\n\");\n }\n }\n}\n\ninterface ChatUsage {\n prompt_tokens?: number;\n completion_tokens?: number;\n prompt_tokens_details?: { cached_tokens?: number };\n /**\n * What this generation was charged. OpenRouter sets it on every response and\n * calls it credits, whose base currency is US dollars; plain OpenAI and the\n * self-hosted endpoints on this wire do not send it at all, so it is absent\n * rather than zero there.\n */\n cost?: number;\n}\ninterface ChatFrame {\n model?: string;\n usage?: ChatUsage;\n choices?: readonly {\n finish_reason?: string | null;\n delta?: {\n content?: string;\n reasoning?: string;\n tool_calls?: readonly { index: number; id?: string; function?: { name?: string; arguments?: string } }[];\n };\n }[];\n}\n"]}
package/dist/render.d.ts CHANGED
@@ -18,7 +18,7 @@
18
18
  * that cannot fail, so a terminal channel is *nothing but* this projection.
19
19
  *
20
20
  * **The answer and the stream are different things, and only one is a message.**
21
- * `RunResult.output` is what `finished` hands to `Delivery.input`: the same
21
+ * `RunResult.output` is what `beforeStop` hands to `Delivery.input`: the same
22
22
  * `Content`, whether the recipient is a person or another session. The stream
23
23
  * is not a message and never becomes one — a `cancelled` or `failed` result has
24
24
  * no `output` field at all, so a run can stream a paragraph and produce nothing
package/dist/render.js CHANGED
@@ -189,6 +189,11 @@ function createRenderer(sink) {
189
189
  /** A delta: written, and recorded as the part of the coming entry already shown. */
190
190
  const say = (text) => { streamed += text; emit(text); };
191
191
  const entry = (stored) => {
192
+ if (stored.type === "hook.input") {
193
+ if (debugging)
194
+ line(dim(`◦ ${stored.hook} · ${oneLine(readable(stored.input), 72)}`));
195
+ return;
196
+ }
192
197
  if (stored.type === "run.started") {
193
198
  // Input with no `from` is what the caller just passed in. Echoing it says
194
199
  // everything twice. Input *with* one came from somewhere else — a
@@ -277,6 +282,10 @@ function createRenderer(sink) {
277
282
  };
278
283
  return {
279
284
  update(next) {
285
+ if (next.type === "hook.error") {
286
+ line(`Hook ${next.hook}: ${next.message}`);
287
+ return;
288
+ }
280
289
  if (next.type === "text.delta") {
281
290
  say(next.text);
282
291
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAsDA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAuDtC;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAE5E,MAAM,GAAG,GAAG,SAAS,CAAC;AACtB,MAAM,GAAG,GAAG,UAAU,CAAC;AACvB,MAAM,KAAK,GAAG,UAAU,CAAC;AACzB,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB,2DAA2D;AAC3D,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,KAAK,GAAG,EAAE,EAAU,EAAE;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AACrE,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,CAAC,KAAa,EAAU,EAAE,CACtC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC;AAE/D,MAAM,OAAO,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AAErE;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,OAAgB;IAChC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAiB,EAAE,EAAE,CACvC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;QAC9B,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,SAAS,GAAG;YACrD,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,GAAG;gBAChE,CAAC,CAAC,WAAW,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,cAAc,CAAC,IAAU;IAChC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,UAAU,CAAC;IACzC,MAAM,SAAS,GAAG,MAAM,KAAK,UAAU,CAAC;IACxC,0EAA0E;IAC1E,MAAM,QAAQ,GAAG,SAAS,IAAI,MAAM,KAAK,UAAU,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACjD,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC;IAEtB,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1E,MAAM,IAAI,GAAG,CAAC,KAAa,EAAE,GAAY,EAAE,EAAE,CAC3C,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IAE5D,oGAAoG;IACpG,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACxC,oEAAoE;IACpE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,4FAA4F;IAC5F,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB;;;;;;;;;;;;OAYG;IACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,MAAkD,CAAC;IACvD,kGAAkG;IAClG,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB;;;;;;;OAOG;IACH,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,KAAK,GAAG,KAAK,CAAC;QACd,MAAM,CAAC,UAAU,CAAC,CAAC;IACrB,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE;QAC5B,YAAY,EAAE,CAAC;QACf,IAAI,IAAI,EAAE,CAAC;YAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAAC,IAAI,GAAG,KAAK,CAAC;QAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,IAAI,MAAM,EAAE,CAAC;YAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAAC,MAAM,GAAG,SAAS,CAAC;QAAC,CAAC;QAC1D,YAAY,EAAE,CAAC;IACjB,CAAC,CAAC;IAEF;;;;;;OAMG;IACH,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE;QACzC,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3F,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;QAChC,OAAO,GAAG,KAAK,GAAG,KAAK,IAAI,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;IACpH,CAAC,CAAC;IAEF;;;;;;;OAOG;IACH,MAAM,WAAW,GAAG,GAAG,EAAE;QACvB,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,OAAO;QAChC,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE;YACxB,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI;gBAAE,OAAO;YAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,OAAO,CAAC,MAAM,CAAE,CAAC,CAAC;YAC1D,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,KAAK,GAAG,IAAI,CAAC;YACb,MAAM,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,KAAK,QAAQ,CAAC,CAAC;QAC1C,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC;IACnB,CAAC,CAAC;IAEF,sFAAsF;IACtF,IAAI,IAAI,GAAG,EAAE,CAAC;IAEd;;;;;;;;OAQG;IACH,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAE;QAC5B,YAAY,EAAE,CAAC;QACf,IAAI,QAAQ,EAAE,CAAC;YAAC,QAAQ,GAAG,KAAK,CAAC;QAAC,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YACjB,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC5B,OAAO;QACT,CAAC;QACD,0EAA0E;QAC1E,mEAAmE;QACnE,IAAI,IAAI,IAAI,CAAC;QACb,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC3B,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC;IAEF,oFAAoF;IACpF,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,GAAG,QAAQ,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,EAAE;QAC/B,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YAClC,0EAA0E;YAC1E,kEAAkE;YAClE,0EAA0E;YAC1E,8CAA8C;YAC9C,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;gBAChB,IAAI,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACjH,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,KAAK,IAAI,CAAC,CAAC;YACX,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACtC,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,KAAK,GAAG,QAAQ,CAAC;gBACvB,wEAAwE;gBACxE,wCAAwC;gBACxC,QAAQ,GAAG,EAAE,CAAC;gBACd,IAAI,CAAC,KAAK;oBAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;qBACtD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;oBAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;oBACtC,IAAI,IAAI;wBAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;yBACpD,IAAI,IAAI;wBAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5B,CAAC;qBAAM,CAAC;oBACN,IAAI,IAAI;wBAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBAC3B,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;gBACjD,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,EAAE,CAAC;YAChB,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;gBACtC,mEAAmE;gBACnE,uEAAuE;gBACvE,6CAA6C;gBAC7C,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClC,IAAI,QAAQ;oBAAE,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;YACjC,wEAAwE;YACxE,uEAAuE;YACvE,6DAA6D;YAC7D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,WAAW,EAAE,CAAC;YACd,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACpC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC9B,IAAI,CAAC,OAAO,CAAC,IAAI;gBAAE,UAAU,EAAE,CAAC;YAChC,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACnE,MAAM,IAAI,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;YACjE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;YAC9C,wEAAwE;YACxE,iDAAiD;YACjD,IAAI,MAAM,IAAI,QAAQ,EAAE,CAAC;gBACvB,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;YAClG,CAAC;YACD,sEAAsE;YACtE,kEAAkE;YAClE,yEAAyE;YACzE,IAAI,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACrD,IAAI,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACvE,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,QAAQ;gBAAE,IAAI,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACxE,CAAC;QACD,4EAA4E;QAC5E,0EAA0E;QAC1E,6CAA6C;IAC/C,CAAC,CAAC;IAEF,OAAO;QACL,MAAM,CAAC,IAAI;YACT,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAC3D,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACpC,wEAAwE;gBACxE,sDAAsD;gBACtD,IAAI,CAAC,SAAS;oBAAE,OAAO;gBACvB,IAAI,CAAC,QAAQ,EAAE,CAAC;oBAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;oBAAC,QAAQ,GAAG,IAAI,CAAC;gBAAC,CAAC;gBAC5D,YAAY,EAAE,CAAC;gBACf,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBACvB,OAAO;YACT,CAAC;YACD,wEAAwE;YACxE,sEAAsE;YACtE,wEAAwE;YACxE,yEAAyE;YACzE,2EAA2E;YAC3E,iDAAiD;YACjD,IAAI,IAAI,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBAClC,IAAI,CAAC,SAAS;oBAAE,OAAO;gBACvB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC/D,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBACjE,OAAO;YACT,CAAC;YACD,qEAAqE;YACrE,gEAAgE;YAChE,mEAAmE;YACnE,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACpC,IAAI,SAAS;oBAAE,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxF,OAAO;YACT,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,CAAC,MAAM;YACX,OAAO,CAAC,KAAK,EAAE,CAAC;YAChB,UAAU,EAAE,CAAC;YACb,IAAI,IAAI,EAAE,CAAC;gBAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAAC,IAAI,GAAG,EAAE,CAAC;YAAC,CAAC;YACpC,IAAI,IAAI,EAAE,CAAC;gBAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAAC,IAAI,GAAG,KAAK,CAAC;YAAC,CAAC;YAE7C,MAAM,KAAK,GAAG;gBACZ,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS;gBAC5F,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBAC1F,MAAM,CAAC,KAAK,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;aAChG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YAEvD,sEAAsE;YACtE,4BAA4B;YAC5B,MAAM,KAAK,GAAG;gBACZ,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;gBACxB,GAAG,KAAK,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE;gBAC5C,GAAG,KAAK;aACT,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAEd,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAC/B,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,WAAW,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;gBACpH,OAAO;YACT,CAAC;YACD,oEAAoE;YACpE,sEAAsE;YACtE,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,QAAQ;gBAAE,IAAI,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,MAAM,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC;QAC5F,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAa,EAAE,IAAU;IACvD,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI,GAAG;QAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * A run, as lines someone can read.\n *\n * A root file rather than a module, and a projection rather than a port: this\n * is `textOf` one level out. `toMessages` folds committed entries into what a\n * model is shown; this folds the update stream into what a person is shown. No\n * adapter has to prove anything about it, so there is nothing here for a\n * conformance suite to hold, and nothing below it to rank against.\n *\n * **It is not a channel, and the difference is worth stating because it is\n * subtle.** A channel does three jobs: decide which session a person is talking\n * to, deliver a message exactly once, and put the result in a medium's own\n * shape. The first two are an application's — a mail thread maps to a session,\n * a webhook may arrive twice. Only the third is the same work for every medium,\n * and only the third is here. Email is a channel with no projection at all: it\n * sends `RunResult.output` and never looks at the stream. A terminal is the\n * other extreme — its identity is \"the one session\" and its delivery is a pipe\n * that cannot fail, so a terminal channel is *nothing but* this projection.\n *\n * **The answer and the stream are different things, and only one is a message.**\n * `RunResult.output` is what `finished` hands to `Delivery.input`: the same\n * `Content`, whether the recipient is a person or another session. The stream\n * is not a message and never becomes one — a `cancelled` or `failed` result has\n * no `output` field at all, so a run can stream a paragraph and produce nothing\n * that could be delivered.\n *\n * Showing that paragraph is not a mistake. Watching what an agent was saying\n * when it was cancelled is most of what debugging one is. What would be a\n * mistake is leaving the outcome implicit, so the text reads as an answer that\n * was never given — which is why `finish` always states it, and takes it from\n * the result rather than from whatever happened to stream.\n *\n * **`detail` is the knob that makes one projection serve every medium.** Three\n * levels, because what the knob is graduated in is the *internal stream* and\n * not the conversation: a tool call is the agent talking to itself, while a\n * message arriving from a subagent, a peer or a person is the conversation\n * happening. The log keeps those apart — one is an entry the loop wrote, the\n * other came through `enqueue` — so the projection does too.\n *\n * There is no level below `\"minimal\"`, and there was one. A medium with no\n * second channel says so by having no `status`, which drops the account\n * entirely; a `detail` that meant the same thing was a second way to state a\n * fact the `Sink` already stated. Failures are not behind a level either — an\n * agent that silently did nothing is the one outcome a reader cannot diagnose\n * from what they were shown.\n *\n * Two channels out, because they answer different questions. `write` carries\n * the answer, so redirecting it captures the answer and nothing else. `status`\n * carries what the agent did on the way — tools, provenance, what it spent.\n */\nimport type { AgentRun, RunResult } from \"./agent.js\";\nimport type { Update } from \"./harness/harness.js\";\nimport type { Content, ContentPart } from \"./content.js\";\nimport type { Stored, ToolCall, Usage } from \"./session/entry.js\";\nimport { textOf } from \"./content.js\";\n\nexport interface Sink {\n /** The answer: assistant text, exactly as it streamed. */\n write(text: string): void;\n /**\n * Everything else: tools, provenance, what the run spent.\n *\n * Omit it and that account is **dropped**, not folded into `write`. Falling\n * back would break the one promise `write` makes — that it holds the answer\n * and nothing else — for every caller who only wanted the answer and did not\n * think to say so.\n */\n status?(line: string): void;\n /** Enables colour and the elapsed-time ticker. A caller without a terminal leaves it off. */\n tty?: boolean;\n /**\n * How much of the run to show.\n *\n * `\"minimal\"` is the conversation: the answer, input that arrived from\n * somewhere else, and anything that failed. `\"standard\"` adds the internal\n * stream — tool calls, their results, and what the run spent. `\"detailed\"`\n * adds what it was thinking, arguments as they form, and the structured\n * `details` a tool returned.\n *\n * For the answer and nothing whatever beside it, give a sink with no\n * `status`.\n */\n detail?: \"minimal\" | \"standard\" | \"detailed\";\n /**\n * Marks a run being watched rather than driven — a subagent taken off the\n * queue, most often. A labelled run has no answer channel: nobody asked it\n * anything, so everything it says is an account of what the run it belongs\n * to is doing, and all of it is prefixed and sent to `status`.\n */\n label?: string;\n /** Milliseconds, for durations. Supplied by a test so its output is exact. */\n now?(): number;\n}\n\n/**\n * Not exported, and that is the gate's doing rather than an oversight.\n *\n * `renderRun` is the whole of what a caller needs, and nothing here composes a\n * renderer by hand. A surface that pushes updates itself — an HTTP stream, a\n * Slack message it keeps editing — would want this, and it can be exported the\n * day one exists. Surface without a consumer is surface nobody is holding to\n * account.\n */\ninterface Renderer {\n update(update: Update): void;\n /** The outcome and what it spent. */\n finish(result: RunResult): void;\n}\n\n/**\n * The one animation, and it is data rather than a dependency.\n *\n * Braille dots because they are one column wide in every terminal that has the\n * font and degrade to boxes rather than to a broken layout in the ones that do\n * not. Written to `status`, so the answer channel stays something a pipe can\n * read.\n */\nconst SPINNER = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"] as const;\n\nconst DIM = \"\\x1b[2m\";\nconst RED = \"\\x1b[31m\";\nconst GREEN = \"\\x1b[32m\";\nconst RESET = \"\\x1b[0m\";\n\n/** One line, however long the thing being described is. */\nconst oneLine = (text: string, limit = 96): string => {\n const flat = text.replace(/\\s+/g, \" \").trim();\n return flat.length > limit ? `${flat.slice(0, limit - 1)}…` : flat;\n};\n\nconst round = (count: number): string =>\n count >= 1000 ? `${(count / 1000).toFixed(1)}k` : `${count}`;\n\nconst seconds = (ms: number): string => `${(ms / 1000).toFixed(1)}s`;\n\n/**\n * Content as the model sees it, plus a marker for every part it cannot.\n *\n * `textOf` drops images, files and opaque blocks, which is right for a model\n * and wrong for a person: a screen showing less than the log holds, silently,\n * is the omission this package refuses everywhere else.\n */\nfunction readable(content: Content): string {\n if (typeof content === \"string\") return content;\n return content.map((part: ContentPart) =>\n part.type === \"text\" ? part.text\n : part.type === \"image\" ? `[image ${part.mediaType}]`\n : part.type === \"file\" ? `[file ${part.name ?? part.mediaType}]`\n : `[opaque ${part.provider}]`).join(\"\\n\");\n}\n\nfunction createRenderer(sink: Sink): Renderer {\n const status = sink.status ?? (() => {});\n const detail = sink.detail ?? \"standard\";\n const debugging = detail === \"detailed\";\n /** The agent talking to its own tools, as opposed to the conversation. */\n const internal = debugging || detail === \"standard\";\n const now = sink.now ?? (() => Date.now());\n const label = sink.label ? `${sink.label} ` : \"\";\n const started = now();\n\n const dim = (text: string) => (sink.tty ? `${DIM}${text}${RESET}` : text);\n const mark = (glyph: string, bad: boolean) =>\n sink.tty ? `${bad ? RED : GREEN}${glyph}${RESET}` : glyph;\n\n /** Names by call id. A tool entry carries only the id; the assistant entry above holds the name. */\n const names = new Map<string, string>();\n /** When each call started, so a result can say how long it took. */\n const clocks = new Map<string, number>();\n /**\n * What has been streamed since the last assistant entry.\n *\n * The harnesses disagree about deltas — some stream tokens, some emit one\n * delta per whole message, and some emit none at all. The entry stream is\n * the one channel every harness fills, so\n * the entry decides what is shown and the deltas decide how much of it\n * already has been.\n *\n * The rule the port does not state, and this therefore does not assume:\n * deltas preceding an assistant entry *ought* to concatenate to its text.\n * Where they do, the remainder is printed and nothing appears twice. Where\n * they are absent, the whole entry is printed. Where they are neither — a\n * harness that streamed something else, or stopped halfway — the entry is\n * printed in full on a fresh line, because showing a reader the same words\n * twice is a smaller failure than silently dropping the half nobody streamed.\n */\n let streamed = \"\";\n let turns = 0;\n /** Whether the last thing written ended mid-line, so a status line can open a fresh one. */\n let open = false;\n let thinking = false;\n\n /**\n * The calls in flight, and the one status line describing all of them.\n *\n * One ticker for the renderer rather than one per call. A tool call is not\n * exclusive — a harness may start four at once, and Pi does — and a per-call\n * interval meant each `tool.started` overwrote the last without clearing it:\n * the orphans kept drawing to the same line for the life of the process,\n * fighting each other and interleaving with committed lines. A terminal has\n * one status line, so there is one thing drawing it.\n *\n * No `at` is kept here. `clocks` already holds when each call started, and a\n * second copy is a second answer.\n */\n const running = new Set<string>();\n let ticker: ReturnType<typeof setInterval> | undefined;\n /** Whether the status line currently holds a spinner that must be erased before anything else. */\n let drawn = false;\n\n /**\n * Erase the spinner, leaving the cursor at the start of a clean line.\n *\n * Called before *every* write, because `\\r` returns to the start of whatever\n * line is current and half a streamed sentence is as much a casualty as a\n * status line. The ticker redraws on its next tick if work is still in\n * flight, so this costs nothing but a frame.\n */\n const clearSpinner = () => {\n if (!drawn) return;\n drawn = false;\n status(\"\\r\\x1b[K\");\n };\n\n const line = (text: string) => {\n clearSpinner();\n if (open) { sink.write(\"\\n\"); open = false; }\n status(`${label}${text}\\n`);\n };\n\n const stopTicker = () => {\n if (ticker) { clearInterval(ticker); ticker = undefined; }\n clearSpinner();\n };\n\n /**\n * What is running, in the width of one line.\n *\n * The oldest call names itself and carries the clock, because it is the one a\n * reader is waiting on; the rest are a count. Naming all four would be a\n * line that changes width every time one finishes.\n */\n const inFlight = (glyph: string): string => {\n const oldest = [...running].sort((a, b) => (clocks.get(a) ?? 0) - (clocks.get(b) ?? 0))[0];\n if (oldest === undefined) return \"\";\n const name = names.get(oldest) ?? oldest.slice(0, 8);\n const others = running.size - 1;\n return `${label}${glyph} ${name}${others ? ` +${others}` : \"\"} ${seconds(now() - (clocks.get(oldest) ?? now()))}`;\n };\n\n /**\n * The one piece of cursor control in this file, and the only reason a long\n * command is not silence. Unref'd so it can never hold a process open.\n *\n * It does not draw over an open line: `say` leaves the cursor mid-sentence\n * while text streams, and `\\r` there would eat the sentence. Nothing is lost\n * — the next tick after the newline draws it.\n */\n const startTicker = () => {\n if (ticker || !sink.tty) return;\n let frame = 0;\n ticker = setInterval(() => {\n if (open || !running.size) return;\n const text = inFlight(SPINNER[frame++ % SPINNER.length]!);\n if (!text) return;\n drawn = true;\n status(`\\r${DIM}${text}${RESET}\\x1b[K`);\n }, 90);\n ticker.unref?.();\n };\n\n /** Partial line held back while a labelled run streams, so the prefix stays whole. */\n let held = \"\";\n\n /**\n * Write text for the reader. The primitive; `say` is the streaming door to it.\n *\n * Only a delta counts as *streamed*. An entry printed through here must not\n * be added to `streamed`, or its own words become the \"already shown\" prefix\n * the next entry is measured against — and since a second turn's text does\n * not begin with a first turn's, `startsWith` failed and the whole turn was\n * printed again under the half that had already streamed.\n */\n const emit = (text: string) => {\n clearSpinner();\n if (thinking) { thinking = false; }\n if (!sink.label) {\n sink.write(text);\n open = !text.endsWith(\"\\n\");\n return;\n }\n // A prefix cannot be applied to half a line, so a labelled run buffers to\n // the newline. Nothing is lost: `finish` flushes whatever is left.\n held += text;\n let cut = held.indexOf(\"\\n\");\n while (cut !== -1) {\n line(held.slice(0, cut));\n held = held.slice(cut + 1);\n cut = held.indexOf(\"\\n\");\n }\n };\n\n /** A delta: written, and recorded as the part of the coming entry already shown. */\n const say = (text: string) => { streamed += text; emit(text); };\n\n const entry = (stored: Stored) => {\n if (stored.type === \"run.started\") {\n // Input with no `from` is what the caller just passed in. Echoing it says\n // everything twice. Input *with* one came from somewhere else — a\n // subagent's report, a peer, a person on a channel — and saying so is the\n // only way that arrival is visible as itself.\n if (stored.from) {\n line(dim(`◦ from ${stored.from.kind} ${stored.from.id.slice(0, 8)} · ${oneLine(readable(stored.input), 72)}`));\n }\n return;\n }\n if (stored.type === \"assistant\") {\n turns += 1;\n const said = readable(stored.content);\n if (said) {\n const shown = streamed;\n // Cleared first: `say` appends to it, and what matters is what had been\n // streamed *before* this entry arrived.\n streamed = \"\";\n if (!shown) emit(said.endsWith(\"\\n\") ? said : `${said}\\n`);\n else if (said.startsWith(shown)) {\n const rest = said.slice(shown.length);\n if (rest) emit(rest.endsWith(\"\\n\") ? rest : `${rest}\\n`);\n else if (open) emit(\"\\n\");\n } else {\n if (open) sink.write(\"\\n\");\n emit(said.endsWith(\"\\n\") ? said : `${said}\\n`);\n }\n } else {\n streamed = \"\";\n }\n for (const call of stored.calls ?? []) {\n // The name is recorded whatever the level: a result rendered later\n // reads it, and a reader who turns detail up mid-run should not meet a\n // call id where every other line has a name.\n names.set(call.callId, call.name);\n if (internal) line(dim(`→ ${call.name} ${oneLine(call.arguments, 72)}`));\n }\n return;\n }\n if (stored.type === \"tool.started\") {\n clocks.set(stored.callId, now());\n // At every level, unlike everything else about a call. A spinner is not\n // an account of what the agent did — it leaves no trace, and it is the\n // only thing between a reader and thirty seconds of silence.\n running.add(stored.callId);\n startTicker();\n return;\n }\n if (stored.type === \"tool.finished\") {\n running.delete(stored.callId);\n if (!running.size) stopTicker();\n const at = clocks.get(stored.callId);\n clocks.delete(stored.callId);\n const name = names.get(stored.callId) ?? stored.callId.slice(0, 8);\n const took = at === undefined ? \"\" : ` · ${seconds(now() - at)}`;\n const body = oneLine(readable(stored.result.content), 64);\n const failed = stored.result.isError === true;\n // A success is the agent working; a failure is something the reader has\n // to know about whatever they asked to be shown.\n if (failed || internal) {\n line(`${mark(failed ? \"✗\" : \"✓\", failed)} ${dim(`${name}${took}${body ? ` · ${body}` : \"\"}`)}`);\n }\n // `details` is the structured channel for an application's own UI and\n // holds whatever that application put there — which may include a\n // credential. Asked for explicitly it is shown; it is never volunteered.\n if (debugging && stored.result.details !== undefined) {\n line(dim(` ${oneLine(JSON.stringify(stored.result.details), 96)}`));\n }\n return;\n }\n if (stored.type === \"summary\") {\n if (internal) line(dim(`· compacted through seq ${stored.replaces}`));\n }\n // `run.finished` is not rendered here. `finish` owns the last line, because\n // `RunResult` carries what the entry cannot: what the run spent, on every\n // outcome including the ones with no answer.\n };\n\n return {\n update(next) {\n if (next.type === \"text.delta\") { say(next.text); return; }\n if (next.type === \"reasoning.delta\") {\n // Reasoning can outweigh the answer several times over, so it is behind\n // `\"detailed\"` rather than dimmed and always present.\n if (!debugging) return;\n if (!thinking) { line(dim(\"· thinking\")); thinking = true; }\n clearSpinner();\n status(dim(next.text));\n return;\n }\n // A harness that reports progress reports it several times per call, in\n // whatever shape its protocol happens to use — ACP forwards its whole\n // `session/update`. That is a debugging channel, not a reading one: the\n // call itself is announced by the entry that carries it, and the name is\n // not even known yet on the first of these. So it sits behind `\"detailed\"`\n // beside `tool-call.delta`, for the same reason.\n if (next.type === \"tool.progress\") {\n if (!debugging) return;\n const name = names.get(next.callId) ?? next.callId.slice(0, 8);\n line(dim(`⋯ ${name} ${oneLine(JSON.stringify(next.data), 64)}`));\n return;\n }\n // Arguments as they form are unparseable until complete and only one\n // harness emits them, so they are worth nothing to a reader and\n // everything to someone asking why a call came out the way it did.\n if (next.type === \"tool-call.delta\") {\n if (debugging) line(dim(` ${next.callId.slice(0, 8)} ${oneLine(next.arguments, 64)}`));\n return;\n }\n if (next.type === \"entry\") entry(next.entry);\n },\n\n finish(result) {\n running.clear();\n stopTicker();\n if (held) { line(held); held = \"\"; }\n if (open) { sink.write(\"\\n\"); open = false; }\n\n const spent = [\n result.usage.inputTokens !== undefined ? `${round(result.usage.inputTokens)} in` : undefined,\n result.usage.cacheReadTokens ? `${round(result.usage.cacheReadTokens)} cached` : undefined,\n result.usage.outputTokens !== undefined ? `${round(result.usage.outputTokens)} out` : undefined,\n ].filter((part): part is string => part !== undefined);\n\n // A count nobody reported is not a zero, so an absent one is left out\n // rather than printed as 0.\n const parts = [\n seconds(now() - started),\n `${turns} ${turns === 1 ? \"turn\" : \"turns\"}`,\n ...spent,\n ].join(\" · \");\n\n if (result.status === \"failed\") {\n line(`${mark(\"✗\", true)} ${dim(`failed (${result.error.code}): ${oneLine(result.error.message, 80)} · ${parts}`)}`);\n return;\n }\n // `cancelled` is an ending nobody asked for, so it is said at every\n // level that says anything. A clean completion is an accounting line.\n if (result.status === \"cancelled\" || internal) line(dim(`— ${result.status} · ${parts}`));\n },\n };\n}\n\n/**\n * Render a run to its end, and answer what it did.\n *\n * The loop every application writes by hand, with the two things a hand-written\n * one keeps getting wrong: everything the stream carries besides text, and an\n * outcome taken from the result rather than from whatever happened to stream.\n */\nexport async function renderRun(run: AgentRun, sink: Sink): Promise<RunResult> {\n const renderer = createRenderer(sink);\n for await (const update of run) renderer.update(update);\n const result = await run.result;\n renderer.finish(result);\n return result;\n}\n\n/** Re-exported so a caller composing a sink imports one thing. */\nexport type { Update, RunResult, Usage, ToolCall };\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A run, as lines someone can read.\n *\n * A root file rather than a module, and a projection rather than a port: this\n * is `textOf` one level out. `toMessages` folds committed entries into what a\n * model is shown; this folds the update stream into what a person is shown. No\n * adapter has to prove anything about it, so there is nothing here for a\n * conformance suite to hold, and nothing below it to rank against.\n *\n * **It is not a channel, and the difference is worth stating because it is\n * subtle.** A channel does three jobs: decide which session a person is talking\n * to, deliver a message exactly once, and put the result in a medium's own\n * shape. The first two are an application's — a mail thread maps to a session,\n * a webhook may arrive twice. Only the third is the same work for every medium,\n * and only the third is here. Email is a channel with no projection at all: it\n * sends `RunResult.output` and never looks at the stream. A terminal is the\n * other extreme — its identity is \"the one session\" and its delivery is a pipe\n * that cannot fail, so a terminal channel is *nothing but* this projection.\n *\n * **The answer and the stream are different things, and only one is a message.**\n * `RunResult.output` is what `beforeStop` hands to `Delivery.input`: the same\n * `Content`, whether the recipient is a person or another session. The stream\n * is not a message and never becomes one — a `cancelled` or `failed` result has\n * no `output` field at all, so a run can stream a paragraph and produce nothing\n * that could be delivered.\n *\n * Showing that paragraph is not a mistake. Watching what an agent was saying\n * when it was cancelled is most of what debugging one is. What would be a\n * mistake is leaving the outcome implicit, so the text reads as an answer that\n * was never given — which is why `finish` always states it, and takes it from\n * the result rather than from whatever happened to stream.\n *\n * **`detail` is the knob that makes one projection serve every medium.** Three\n * levels, because what the knob is graduated in is the *internal stream* and\n * not the conversation: a tool call is the agent talking to itself, while a\n * message arriving from a subagent, a peer or a person is the conversation\n * happening. The log keeps those apart — one is an entry the loop wrote, the\n * other came through `enqueue` — so the projection does too.\n *\n * There is no level below `\"minimal\"`, and there was one. A medium with no\n * second channel says so by having no `status`, which drops the account\n * entirely; a `detail` that meant the same thing was a second way to state a\n * fact the `Sink` already stated. Failures are not behind a level either — an\n * agent that silently did nothing is the one outcome a reader cannot diagnose\n * from what they were shown.\n *\n * Two channels out, because they answer different questions. `write` carries\n * the answer, so redirecting it captures the answer and nothing else. `status`\n * carries what the agent did on the way — tools, provenance, what it spent.\n */\nimport type { AgentRun, RunResult } from \"./agent.js\";\nimport type { Update } from \"./harness/harness.js\";\nimport type { Content, ContentPart } from \"./content.js\";\nimport type { Stored, ToolCall, Usage } from \"./session/entry.js\";\nimport { textOf } from \"./content.js\";\n\nexport interface Sink {\n /** The answer: assistant text, exactly as it streamed. */\n write(text: string): void;\n /**\n * Everything else: tools, provenance, what the run spent.\n *\n * Omit it and that account is **dropped**, not folded into `write`. Falling\n * back would break the one promise `write` makes — that it holds the answer\n * and nothing else — for every caller who only wanted the answer and did not\n * think to say so.\n */\n status?(line: string): void;\n /** Enables colour and the elapsed-time ticker. A caller without a terminal leaves it off. */\n tty?: boolean;\n /**\n * How much of the run to show.\n *\n * `\"minimal\"` is the conversation: the answer, input that arrived from\n * somewhere else, and anything that failed. `\"standard\"` adds the internal\n * stream — tool calls, their results, and what the run spent. `\"detailed\"`\n * adds what it was thinking, arguments as they form, and the structured\n * `details` a tool returned.\n *\n * For the answer and nothing whatever beside it, give a sink with no\n * `status`.\n */\n detail?: \"minimal\" | \"standard\" | \"detailed\";\n /**\n * Marks a run being watched rather than driven — a subagent taken off the\n * queue, most often. A labelled run has no answer channel: nobody asked it\n * anything, so everything it says is an account of what the run it belongs\n * to is doing, and all of it is prefixed and sent to `status`.\n */\n label?: string;\n /** Milliseconds, for durations. Supplied by a test so its output is exact. */\n now?(): number;\n}\n\n/**\n * Not exported, and that is the gate's doing rather than an oversight.\n *\n * `renderRun` is the whole of what a caller needs, and nothing here composes a\n * renderer by hand. A surface that pushes updates itself — an HTTP stream, a\n * Slack message it keeps editing — would want this, and it can be exported the\n * day one exists. Surface without a consumer is surface nobody is holding to\n * account.\n */\ninterface Renderer {\n update(update: Update): void;\n /** The outcome and what it spent. */\n finish(result: RunResult): void;\n}\n\n/**\n * The one animation, and it is data rather than a dependency.\n *\n * Braille dots because they are one column wide in every terminal that has the\n * font and degrade to boxes rather than to a broken layout in the ones that do\n * not. Written to `status`, so the answer channel stays something a pipe can\n * read.\n */\nconst SPINNER = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"] as const;\n\nconst DIM = \"\\x1b[2m\";\nconst RED = \"\\x1b[31m\";\nconst GREEN = \"\\x1b[32m\";\nconst RESET = \"\\x1b[0m\";\n\n/** One line, however long the thing being described is. */\nconst oneLine = (text: string, limit = 96): string => {\n const flat = text.replace(/\\s+/g, \" \").trim();\n return flat.length > limit ? `${flat.slice(0, limit - 1)}…` : flat;\n};\n\nconst round = (count: number): string =>\n count >= 1000 ? `${(count / 1000).toFixed(1)}k` : `${count}`;\n\nconst seconds = (ms: number): string => `${(ms / 1000).toFixed(1)}s`;\n\n/**\n * Content as the model sees it, plus a marker for every part it cannot.\n *\n * `textOf` drops images, files and opaque blocks, which is right for a model\n * and wrong for a person: a screen showing less than the log holds, silently,\n * is the omission this package refuses everywhere else.\n */\nfunction readable(content: Content): string {\n if (typeof content === \"string\") return content;\n return content.map((part: ContentPart) =>\n part.type === \"text\" ? part.text\n : part.type === \"image\" ? `[image ${part.mediaType}]`\n : part.type === \"file\" ? `[file ${part.name ?? part.mediaType}]`\n : `[opaque ${part.provider}]`).join(\"\\n\");\n}\n\nfunction createRenderer(sink: Sink): Renderer {\n const status = sink.status ?? (() => {});\n const detail = sink.detail ?? \"standard\";\n const debugging = detail === \"detailed\";\n /** The agent talking to its own tools, as opposed to the conversation. */\n const internal = debugging || detail === \"standard\";\n const now = sink.now ?? (() => Date.now());\n const label = sink.label ? `${sink.label} ` : \"\";\n const started = now();\n\n const dim = (text: string) => (sink.tty ? `${DIM}${text}${RESET}` : text);\n const mark = (glyph: string, bad: boolean) =>\n sink.tty ? `${bad ? RED : GREEN}${glyph}${RESET}` : glyph;\n\n /** Names by call id. A tool entry carries only the id; the assistant entry above holds the name. */\n const names = new Map<string, string>();\n /** When each call started, so a result can say how long it took. */\n const clocks = new Map<string, number>();\n /**\n * What has been streamed since the last assistant entry.\n *\n * The harnesses disagree about deltas — some stream tokens, some emit one\n * delta per whole message, and some emit none at all. The entry stream is\n * the one channel every harness fills, so\n * the entry decides what is shown and the deltas decide how much of it\n * already has been.\n *\n * The rule the port does not state, and this therefore does not assume:\n * deltas preceding an assistant entry *ought* to concatenate to its text.\n * Where they do, the remainder is printed and nothing appears twice. Where\n * they are absent, the whole entry is printed. Where they are neither — a\n * harness that streamed something else, or stopped halfway — the entry is\n * printed in full on a fresh line, because showing a reader the same words\n * twice is a smaller failure than silently dropping the half nobody streamed.\n */\n let streamed = \"\";\n let turns = 0;\n /** Whether the last thing written ended mid-line, so a status line can open a fresh one. */\n let open = false;\n let thinking = false;\n\n /**\n * The calls in flight, and the one status line describing all of them.\n *\n * One ticker for the renderer rather than one per call. A tool call is not\n * exclusive — a harness may start four at once, and Pi does — and a per-call\n * interval meant each `tool.started` overwrote the last without clearing it:\n * the orphans kept drawing to the same line for the life of the process,\n * fighting each other and interleaving with committed lines. A terminal has\n * one status line, so there is one thing drawing it.\n *\n * No `at` is kept here. `clocks` already holds when each call started, and a\n * second copy is a second answer.\n */\n const running = new Set<string>();\n let ticker: ReturnType<typeof setInterval> | undefined;\n /** Whether the status line currently holds a spinner that must be erased before anything else. */\n let drawn = false;\n\n /**\n * Erase the spinner, leaving the cursor at the start of a clean line.\n *\n * Called before *every* write, because `\\r` returns to the start of whatever\n * line is current and half a streamed sentence is as much a casualty as a\n * status line. The ticker redraws on its next tick if work is still in\n * flight, so this costs nothing but a frame.\n */\n const clearSpinner = () => {\n if (!drawn) return;\n drawn = false;\n status(\"\\r\\x1b[K\");\n };\n\n const line = (text: string) => {\n clearSpinner();\n if (open) { sink.write(\"\\n\"); open = false; }\n status(`${label}${text}\\n`);\n };\n\n const stopTicker = () => {\n if (ticker) { clearInterval(ticker); ticker = undefined; }\n clearSpinner();\n };\n\n /**\n * What is running, in the width of one line.\n *\n * The oldest call names itself and carries the clock, because it is the one a\n * reader is waiting on; the rest are a count. Naming all four would be a\n * line that changes width every time one finishes.\n */\n const inFlight = (glyph: string): string => {\n const oldest = [...running].sort((a, b) => (clocks.get(a) ?? 0) - (clocks.get(b) ?? 0))[0];\n if (oldest === undefined) return \"\";\n const name = names.get(oldest) ?? oldest.slice(0, 8);\n const others = running.size - 1;\n return `${label}${glyph} ${name}${others ? ` +${others}` : \"\"} ${seconds(now() - (clocks.get(oldest) ?? now()))}`;\n };\n\n /**\n * The one piece of cursor control in this file, and the only reason a long\n * command is not silence. Unref'd so it can never hold a process open.\n *\n * It does not draw over an open line: `say` leaves the cursor mid-sentence\n * while text streams, and `\\r` there would eat the sentence. Nothing is lost\n * — the next tick after the newline draws it.\n */\n const startTicker = () => {\n if (ticker || !sink.tty) return;\n let frame = 0;\n ticker = setInterval(() => {\n if (open || !running.size) return;\n const text = inFlight(SPINNER[frame++ % SPINNER.length]!);\n if (!text) return;\n drawn = true;\n status(`\\r${DIM}${text}${RESET}\\x1b[K`);\n }, 90);\n ticker.unref?.();\n };\n\n /** Partial line held back while a labelled run streams, so the prefix stays whole. */\n let held = \"\";\n\n /**\n * Write text for the reader. The primitive; `say` is the streaming door to it.\n *\n * Only a delta counts as *streamed*. An entry printed through here must not\n * be added to `streamed`, or its own words become the \"already shown\" prefix\n * the next entry is measured against — and since a second turn's text does\n * not begin with a first turn's, `startsWith` failed and the whole turn was\n * printed again under the half that had already streamed.\n */\n const emit = (text: string) => {\n clearSpinner();\n if (thinking) { thinking = false; }\n if (!sink.label) {\n sink.write(text);\n open = !text.endsWith(\"\\n\");\n return;\n }\n // A prefix cannot be applied to half a line, so a labelled run buffers to\n // the newline. Nothing is lost: `finish` flushes whatever is left.\n held += text;\n let cut = held.indexOf(\"\\n\");\n while (cut !== -1) {\n line(held.slice(0, cut));\n held = held.slice(cut + 1);\n cut = held.indexOf(\"\\n\");\n }\n };\n\n /** A delta: written, and recorded as the part of the coming entry already shown. */\n const say = (text: string) => { streamed += text; emit(text); };\n\n const entry = (stored: Stored) => {\n if (stored.type === \"hook.input\") {\n if (debugging) line(dim(`◦ ${stored.hook} · ${oneLine(readable(stored.input), 72)}`));\n return;\n }\n if (stored.type === \"run.started\") {\n // Input with no `from` is what the caller just passed in. Echoing it says\n // everything twice. Input *with* one came from somewhere else — a\n // subagent's report, a peer, a person on a channel — and saying so is the\n // only way that arrival is visible as itself.\n if (stored.from) {\n line(dim(`◦ from ${stored.from.kind} ${stored.from.id.slice(0, 8)} · ${oneLine(readable(stored.input), 72)}`));\n }\n return;\n }\n if (stored.type === \"assistant\") {\n turns += 1;\n const said = readable(stored.content);\n if (said) {\n const shown = streamed;\n // Cleared first: `say` appends to it, and what matters is what had been\n // streamed *before* this entry arrived.\n streamed = \"\";\n if (!shown) emit(said.endsWith(\"\\n\") ? said : `${said}\\n`);\n else if (said.startsWith(shown)) {\n const rest = said.slice(shown.length);\n if (rest) emit(rest.endsWith(\"\\n\") ? rest : `${rest}\\n`);\n else if (open) emit(\"\\n\");\n } else {\n if (open) sink.write(\"\\n\");\n emit(said.endsWith(\"\\n\") ? said : `${said}\\n`);\n }\n } else {\n streamed = \"\";\n }\n for (const call of stored.calls ?? []) {\n // The name is recorded whatever the level: a result rendered later\n // reads it, and a reader who turns detail up mid-run should not meet a\n // call id where every other line has a name.\n names.set(call.callId, call.name);\n if (internal) line(dim(`→ ${call.name} ${oneLine(call.arguments, 72)}`));\n }\n return;\n }\n if (stored.type === \"tool.started\") {\n clocks.set(stored.callId, now());\n // At every level, unlike everything else about a call. A spinner is not\n // an account of what the agent did — it leaves no trace, and it is the\n // only thing between a reader and thirty seconds of silence.\n running.add(stored.callId);\n startTicker();\n return;\n }\n if (stored.type === \"tool.finished\") {\n running.delete(stored.callId);\n if (!running.size) stopTicker();\n const at = clocks.get(stored.callId);\n clocks.delete(stored.callId);\n const name = names.get(stored.callId) ?? stored.callId.slice(0, 8);\n const took = at === undefined ? \"\" : ` · ${seconds(now() - at)}`;\n const body = oneLine(readable(stored.result.content), 64);\n const failed = stored.result.isError === true;\n // A success is the agent working; a failure is something the reader has\n // to know about whatever they asked to be shown.\n if (failed || internal) {\n line(`${mark(failed ? \"✗\" : \"✓\", failed)} ${dim(`${name}${took}${body ? ` · ${body}` : \"\"}`)}`);\n }\n // `details` is the structured channel for an application's own UI and\n // holds whatever that application put there — which may include a\n // credential. Asked for explicitly it is shown; it is never volunteered.\n if (debugging && stored.result.details !== undefined) {\n line(dim(` ${oneLine(JSON.stringify(stored.result.details), 96)}`));\n }\n return;\n }\n if (stored.type === \"summary\") {\n if (internal) line(dim(`· compacted through seq ${stored.replaces}`));\n }\n // `run.finished` is not rendered here. `finish` owns the last line, because\n // `RunResult` carries what the entry cannot: what the run spent, on every\n // outcome including the ones with no answer.\n };\n\n return {\n update(next) {\n if (next.type === \"hook.error\") { line(`Hook ${next.hook}: ${next.message}`); return; }\n if (next.type === \"text.delta\") { say(next.text); return; }\n if (next.type === \"reasoning.delta\") {\n // Reasoning can outweigh the answer several times over, so it is behind\n // `\"detailed\"` rather than dimmed and always present.\n if (!debugging) return;\n if (!thinking) { line(dim(\"· thinking\")); thinking = true; }\n clearSpinner();\n status(dim(next.text));\n return;\n }\n // A harness that reports progress reports it several times per call, in\n // whatever shape its protocol happens to use — ACP forwards its whole\n // `session/update`. That is a debugging channel, not a reading one: the\n // call itself is announced by the entry that carries it, and the name is\n // not even known yet on the first of these. So it sits behind `\"detailed\"`\n // beside `tool-call.delta`, for the same reason.\n if (next.type === \"tool.progress\") {\n if (!debugging) return;\n const name = names.get(next.callId) ?? next.callId.slice(0, 8);\n line(dim(`⋯ ${name} ${oneLine(JSON.stringify(next.data), 64)}`));\n return;\n }\n // Arguments as they form are unparseable until complete and only one\n // harness emits them, so they are worth nothing to a reader and\n // everything to someone asking why a call came out the way it did.\n if (next.type === \"tool-call.delta\") {\n if (debugging) line(dim(` ${next.callId.slice(0, 8)} ${oneLine(next.arguments, 64)}`));\n return;\n }\n if (next.type === \"entry\") entry(next.entry);\n },\n\n finish(result) {\n running.clear();\n stopTicker();\n if (held) { line(held); held = \"\"; }\n if (open) { sink.write(\"\\n\"); open = false; }\n\n const spent = [\n result.usage.inputTokens !== undefined ? `${round(result.usage.inputTokens)} in` : undefined,\n result.usage.cacheReadTokens ? `${round(result.usage.cacheReadTokens)} cached` : undefined,\n result.usage.outputTokens !== undefined ? `${round(result.usage.outputTokens)} out` : undefined,\n ].filter((part): part is string => part !== undefined);\n\n // A count nobody reported is not a zero, so an absent one is left out\n // rather than printed as 0.\n const parts = [\n seconds(now() - started),\n `${turns} ${turns === 1 ? \"turn\" : \"turns\"}`,\n ...spent,\n ].join(\" · \");\n\n if (result.status === \"failed\") {\n line(`${mark(\"✗\", true)} ${dim(`failed (${result.error.code}): ${oneLine(result.error.message, 80)} · ${parts}`)}`);\n return;\n }\n // `cancelled` is an ending nobody asked for, so it is said at every\n // level that says anything. A clean completion is an accounting line.\n if (result.status === \"cancelled\" || internal) line(dim(`— ${result.status} · ${parts}`));\n },\n };\n}\n\n/**\n * Render a run to its end, and answer what it did.\n *\n * The loop every application writes by hand, with the two things a hand-written\n * one keeps getting wrong: everything the stream carries besides text, and an\n * outcome taken from the result rather than from whatever happened to stream.\n */\nexport async function renderRun(run: AgentRun, sink: Sink): Promise<RunResult> {\n const renderer = createRenderer(sink);\n for await (const update of run) renderer.update(update);\n const result = await run.result;\n renderer.finish(result);\n return result;\n}\n\n/** Re-exported so a caller composing a sink imports one thing. */\nexport type { Update, RunResult, Usage, ToolCall };\n"]}
package/dist/run.js CHANGED
@@ -12,6 +12,10 @@ import { createExecutor } from "./tools/execute.js";
12
12
  */
13
13
  export function runAgent(options) {
14
14
  // A claim names its own session; a caller sending into a new one names none.
15
+ const hooks = options.agent.hooks ?? [];
16
+ if (new Set(hooks.map(hook => hook.name)).size !== hooks.length || hooks.some(hook => !hook.name)) {
17
+ throw new Error("Lifecycle hook names must be non-empty and unique.");
18
+ }
15
19
  const claim = options.claim;
16
20
  const sessionId = claim?.sessionId ?? options.sessionId ?? crypto.randomUUID();
17
21
  const controller = new AbortController();
@@ -35,6 +39,8 @@ export function runAgent(options) {
35
39
  // we own and a loop we do not — and so a run that failed still says what it
36
40
  // burned on the way there.
37
41
  const usage = {};
42
+ let hookContext;
43
+ let result;
38
44
  try {
39
45
  const { agent, store } = options;
40
46
  const restored = store ? await open(store, sessionId, agent, options.key) : undefined;
@@ -47,7 +53,7 @@ export function runAgent(options) {
47
53
  // The store's own position, not the last entry's. They agree only while
48
54
  // nothing has ever been folded away, and the store is the one that knows.
49
55
  const log = createLog(restored?.value.entries ?? [], restored?.value.seq ?? 0);
50
- const stopping = (error) => ({ status: "failed", error, seq: log.seq, usage });
56
+ const stopping = (error) => (result = { status: "failed", error, seq: log.seq, usage });
51
57
  // A claim was read at a position. If the session has moved since, this
52
58
  // worker lost, and the deliveries it was carrying are still where it
53
59
  // found them.
@@ -155,27 +161,6 @@ export function runAgent(options) {
155
161
  controller.abort();
156
162
  }
157
163
  }
158
- /**
159
- * What the agent wants delivered as this run ends.
160
- *
161
- * A callback that throws must not cost the log its terminal entry: the
162
- * run would stay open, and an open run nothing closes is claimed by
163
- * `interrupted` every window for ever — the trap the no-recovery path above
164
- * exists to close. So the throw is held, the run is ended without the
165
- * deliveries, and it is re-thrown once the log is safe.
166
- */
167
- let reportFailure;
168
- async function reporting(run) {
169
- if (!agent.finished)
170
- return [];
171
- try {
172
- return await agent.finished({ sessionId, ...run });
173
- }
174
- catch (error) {
175
- reportFailure = error;
176
- return [];
177
- }
178
- }
179
164
  // Input that arrived while this activation was running, taken from the
180
165
  // front of the queue and committed as part of it. `takePending` removes
181
166
  // exactly what was committed, in the same write, so losing the position
@@ -233,12 +218,7 @@ export function runAgent(options) {
233
218
  message: `The ${agent.harness.id} harness cannot restart from history, so this activation cannot be continued.`,
234
219
  retryable: false,
235
220
  };
236
- // Whoever was waiting on it is told, in the write that ends it. A
237
- // parent expecting a child that can now never report is the same dead
238
- // end one layer up.
239
- const told = await reporting({ runId: interrupted, outcome: "failed", output: "" });
240
- await commit([{ type: "run.finished", runId: interrupted, outcome: "failed", error: failure }], told);
241
- return stopping(failure);
221
+ stopped = failure;
242
222
  }
243
223
  }
244
224
  // An interrupted predecessor is ended before a new activation begins over
@@ -268,14 +248,14 @@ export function runAgent(options) {
268
248
  // passed was never seen. Checked once here too, before the harness is
269
249
  // handed anything.
270
250
  stopped ??= exceeded();
271
- const outcome = stopped
272
- ? { status: "failed", error: stopped }
273
- : await agent.harness.run({
251
+ function makeContext() {
252
+ return {
274
253
  sessionId, runId,
275
254
  instructions: agent.instructions,
276
255
  history: () => toMessages({
277
256
  instructions: agent.instructions,
278
257
  entries: log.entries,
258
+ ...(agent.attribution ? { attribution: true } : {}),
279
259
  ...(options.context ? { context: options.context } : {}),
280
260
  }),
281
261
  entries: () => log.entries,
@@ -284,34 +264,89 @@ export function runAgent(options) {
284
264
  commit: (entries) => commit(entries),
285
265
  ...(store ? { drain } : {}),
286
266
  emit: (update) => updates.push(update),
287
- signal,
288
- });
267
+ signal, hooks,
268
+ };
269
+ }
270
+ async function stoppingHooks(outcome) {
271
+ const inputs = [];
272
+ const outgoing = [];
273
+ for (const hook of hooks) {
274
+ const response = await hook.beforeStop?.(hookContext, outcome);
275
+ if (response && "input" in response && outcome.status === "completed" && !signal.aborted && !stopped &&
276
+ !log.entries.some(entry => entry.type === "hook.input" && entry.runId === runId && entry.hook === hook.name)) {
277
+ inputs.push({ type: "hook.input", runId, hook: hook.name, input: response.input });
278
+ }
279
+ if (response && "deliveries" in response)
280
+ outgoing.push(...response.deliveries);
281
+ }
282
+ return { inputs, deliveries: outgoing };
283
+ }
284
+ hookContext = makeContext();
285
+ let outcome;
286
+ let outgoing = [];
287
+ try {
288
+ for (const hook of hooks)
289
+ await hook.beforeRun?.(hookContext);
290
+ for (;;) {
291
+ outcome = stopped ? { status: "failed", error: stopped }
292
+ : signal.aborted ? { status: "cancelled" }
293
+ : await agent.harness.run(hookContext);
294
+ if (stopped)
295
+ outcome = { status: "failed", error: stopped };
296
+ else if (signal.aborted)
297
+ outcome = { status: "cancelled" };
298
+ const ending = await stoppingHooks(outcome);
299
+ if (!ending.inputs.length) {
300
+ outgoing = ending.deliveries;
301
+ break;
302
+ }
303
+ await commit(ending.inputs);
304
+ }
305
+ }
306
+ catch (error) {
307
+ if (error instanceof CommitFailed)
308
+ throw error;
309
+ outcome = { status: "failed", error: {
310
+ code: "hook-or-harness", message: error instanceof Error ? error.message : String(error), retryable: false,
311
+ } };
312
+ }
289
313
  // A harness stopped by the ceiling reports a cancellation, because that is
290
314
  // all it saw. The run knows why it was cancelled and says so instead.
291
315
  const settled = stopped && outcome.status === "cancelled"
292
316
  ? { status: "failed", error: stopped }
293
317
  : outcome;
294
- const output = settled.status === "completed" ? settled.output : "";
295
- const finished = await reporting({ runId, outcome: settled.status, output });
296
318
  await commit([{
297
319
  type: "run.finished", runId,
298
320
  outcome: settled.status === "completed" ? "completed" : settled.status === "cancelled" ? "cancelled" : "failed",
299
321
  ...(settled.status === "failed" ? { error: settled.error } : {}),
300
- }], finished);
322
+ }], outgoing);
301
323
  if (settled.status === "completed") {
302
- return { status: "completed", output: settled.output, seq: log.seq, usage };
324
+ return result = { status: "completed", output: settled.output, seq: log.seq, usage };
303
325
  }
304
326
  if (settled.status === "cancelled")
305
- return { status: "cancelled", seq: log.seq, usage };
327
+ return result = { status: "cancelled", seq: log.seq, usage };
306
328
  return stopping(settled.error);
307
329
  }
308
330
  catch (error) {
309
331
  if (error instanceof CommitFailed) {
310
- return { status: "failed", error: error.failure, seq: error.seq, usage };
332
+ return result = { status: "failed", error: error.failure, seq: error.seq, usage };
311
333
  }
312
334
  throw error;
313
335
  }
314
336
  finally {
337
+ if (hookContext) {
338
+ const final = result ?? { status: "failed", error: {
339
+ code: "run-error", message: "The run could not finish.", retryable: false,
340
+ }, seq: hookContext.entries().at(-1)?.seq ?? 0, usage };
341
+ for (const hook of hooks) {
342
+ try {
343
+ await hook.afterRun?.(hookContext, final);
344
+ }
345
+ catch (error) {
346
+ updates.push({ type: "hook.error", hook: hook.name, message: error instanceof Error ? error.message : String(error) });
347
+ }
348
+ }
349
+ }
315
350
  updates.close();
316
351
  }
317
352
  }