@cerebro-labs/agentnava-sdk 0.26.0 → 0.44.1
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.
- package/README.md +204 -23
- package/account.d.ts +154 -0
- package/account.js +219 -0
- package/account.js.map +1 -0
- package/agent.d.ts +217 -88
- package/agent.js +295 -125
- package/agent.js.map +1 -1
- package/apps.generated.d.ts +154 -0
- package/apps.generated.js +152 -0
- package/apps.generated.js.map +1 -0
- package/attach.d.ts +25 -26
- package/attach.js +53 -66
- package/attach.js.map +1 -1
- package/connection.d.ts +79 -82
- package/connection.js +67 -134
- package/connection.js.map +1 -1
- package/conversation.d.ts +55 -35
- package/conversation.js +70 -50
- package/conversation.js.map +1 -1
- package/database.d.ts +53 -0
- package/database.js +69 -0
- package/database.js.map +1 -0
- package/end-user.d.ts +58 -47
- package/end-user.js +62 -72
- package/end-user.js.map +1 -1
- package/errors.d.ts +2 -1
- package/errors.js +2 -1
- package/errors.js.map +1 -1
- package/http.d.ts +5 -0
- package/http.js +109 -8
- package/http.js.map +1 -1
- package/import-tools.d.ts +48 -33
- package/import-tools.js +281 -127
- package/import-tools.js.map +1 -1
- package/index.d.ts +5 -2
- package/index.js +9 -2
- package/index.js.map +1 -1
- package/load-agent-dir.js +28 -5
- package/load-agent-dir.js.map +1 -1
- package/package.json +5 -1
- package/secret.d.ts +1 -1
- package/secret.js +1 -1
- package/secret.js.map +1 -1
- package/templates-inline.js +1 -1
- package/templates-inline.js.map +1 -1
- package/turn.d.ts +1 -1
- package/turn.js +77 -29
- package/turn.js.map +1 -1
- package/types.d.ts +409 -83
- package/types.js.map +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.js.map +1 -1
- package/workflows.d.ts +10 -0
- package/workflows.js +16 -0
- package/workflows.js.map +1 -1
- package/workspace.d.ts +80 -62
- package/workspace.js +92 -75
- package/workspace.js.map +1 -1
- package/connection-status.d.ts +0 -28
- package/connection-status.js +0 -61
- package/connection-status.js.map +0 -1
package/load-agent-dir.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load-agent-dir.js","sourceRoot":"","sources":["../src/load-agent-dir.ts"],"names":[],"mappings":"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","sourcesContent":["// `loadAgentDir` reads a Claude-Code-style folder and returns a SpecInput\n// suitable for `an.specs.push(...)`. Layout it expects:\n//\n// <path>/\n// ├── AGENT.md # required: core instructions (CLAUDE.md equivalent)\n// ├── skills/<name>/SKILL.md # optional, repeated\n// ├── subagents/<name>.md # optional, repeated (advanced)\n// ├── workflows/<name>.md # optional, repeated\n// ├── commands/<name>.md # IGNORED (warned): specs carry no commands\n// ├── http-tools.json # optional, declarative HTTP tool array\n// └── agent.json # optional, capabilities (tools, sandbox, role)\n//\n// Files inside `skills/` use the Claude Code convention: one folder per skill,\n// containing `SKILL.md` with YAML frontmatter. The folder name is the skill name.\n// `subagents/` and `workflows/` are flat directories of `<name>.md`. A\n// `commands/` directory is ignored with a warning: specs no longer carry them.\n//\n// `agent.json` is what makes a folder self-describing: it carries the spec-level\n// capabilities (`tools`, `sandbox`, `role`) alongside the markdown, so an agent\n// directory can be copied between workspaces and still be the same agent. Values\n// passed in `opts` win over the file, so a caller can still override in code.\n//\n// Knowledge base files are NOT loaded by this helper. Use `an.knowledge.upload`\n// for those. Everything else in the bundle is markdown.\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Filesystem, HttpTool, McpServer, SpecFile, SpecInput } from './types.ts';\n\n// Local aliases, so the body of this file reads the way it was written against\n// the retired package's names. Nothing outside this module uses them.\ntype HttpToolDef = HttpTool;\ntype McpServerSpec = McpServer;\ntype AgentRole = string;\ntype SpecSandbox = Filesystem;\ntype SpecToolGroup = string;\ntype WidgetDef = { name: string; [k: string]: unknown };\n\nexport interface LoadAgentDirOptions {\n\t/** Dev-chosen, stable key, required. Used by the backend to upsert this spec. */\n\tkey: string;\n\t/** The role this agent plays. Default `'freelancer'` is applied server-side\n\t * when omitted. See {@link AgentRole} for the full vocabulary. */\n\trole?: AgentRole;\n\t/** Platform tool groups to grant. Overrides `agent.json`. */\n\ttools?: SpecToolGroup[];\n\t/** Filesystem for this agent's turns. Overrides `agent.json`. */\n\tsandbox?: SpecSandbox;\n}\n\n/** The optional `agent.json` manifest, spec-level capabilities that belong with\n * the markdown rather than in whatever script happens to push it. */\ninterface AgentManifest {\n\ttools?: SpecToolGroup[];\n\tsandbox?: SpecSandbox;\n\trole?: AgentRole;\n}\n\nexport function loadAgentDir(path: string, opts: LoadAgentDirOptions): SpecInput {\n\tif (!opts.key) {\n\t\tthrow new Error('[loadAgentDir] opts.key is required');\n\t}\n\tif (!existsSync(path) || !statSync(path).isDirectory()) {\n\t\tthrow new Error(`[loadAgentDir] not a directory: ${path}`);\n\t}\n\n\tconst agentMdPath = join(path, 'AGENT.md');\n\tif (!existsSync(agentMdPath)) {\n\t\tthrow new Error(\n\t\t\t`[loadAgentDir] missing required file: ${agentMdPath}. ` +\n\t\t\t\t`Every spec needs an AGENT.md (the CLAUDE.md equivalent: core instructions).`,\n\t\t);\n\t}\n\tconst instructions = readFileSync(agentMdPath, 'utf8');\n\n\tconst skills = loadSkillsDir(join(path, 'skills'));\n\tconst subagents = loadFlatMdDir(join(path, 'subagents'));\n\tconst workflows = loadFlatMdDir(join(path, 'workflows'));\n\t// `commands/` is read only to WARN about it. A spec has no commands field any\n\t// more: slash commands had one writer (the dedicated-vm translator) and no\n\t// reader on the default runtime.\n\t//\n\t// Deliberately not folded into workflows, which would be the tempting\n\t// one-liner. A Claude Code slash command is invoked BY A PERSON; a workflow is\n\t// invoked by the model whenever it looks relevant. Folding them would make\n\t// every /command fire unbidden, which is a behaviour change disguised as a\n\t// migration. Warned rather than dropped in silence, and rather than thrown,\n\t// so an existing agent directory still loads.\n\tconst commands = loadFlatMdDir(join(path, 'commands'));\n\tif (commands.length > 0) {\n\t\tconsole.warn(\n\t\t\t`[loadAgentDir] ignoring ${commands.length} file(s) in commands/: agent specs no longer ` +\n\t\t\t\t'carry slash commands. Move any that should run automatically into workflows/.',\n\t\t);\n\t}\n\n\tconst out: SpecInput = { key: opts.key, instructions };\n\tif (skills.length > 0) out.skills = skills;\n\tif (subagents.length > 0) out.subagents = subagents;\n\tif (workflows.length > 0) out.workflows = workflows;\n\tconst httpTools = loadHttpTools(join(path, 'http-tools.json'));\n\tif (httpTools && httpTools.length > 0) out.httpTools = httpTools;\n\tconst widgets = loadWidgets(join(path, 'widgets.json'));\n\tif (widgets && widgets.length > 0) out.widgets = widgets;\n\tconst mcpServers = loadMcpServers(join(path, 'mcp-servers.json'));\n\tif (mcpServers && mcpServers.length > 0) out.mcpServers = mcpServers;\n\n\tconst manifest = loadManifest(join(path, 'agent.json'));\n\tconst tools = opts.tools ?? manifest.tools;\n\tif (tools && tools.length > 0) out.tools = tools;\n\tconst sandbox = opts.sandbox ?? manifest.sandbox;\n\tif (sandbox) out.sandbox = sandbox;\n\tconst role = opts.role ?? manifest.role;\n\tif (role) out.role = role;\n\treturn out;\n}\n\n/** agent.json, optional. Absent is the common case and means \"all defaults\". */\nfunction loadManifest(filePath: string): AgentManifest {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return {};\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] agent.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: must be an object');\n\t}\n\tconst m = parsed as Record<string, unknown>;\n\tif (m.tools !== undefined && !Array.isArray(m.tools)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: \"tools\" must be an array');\n\t}\n\tif (m.sandbox !== undefined && m.sandbox !== 'drive' && m.sandbox !== 'none') {\n\t\tthrow new Error(`[loadAgentDir] agent.json: \"sandbox\" must be 'drive' or 'none'`);\n\t}\n\t// Group names are NOT validated here on purpose: the platform is the\n\t// authority on which groups exist and rejects unknown ones at push time with\n\t// the current list, so the SDK never goes stale against a newer platform.\n\treturn m as AgentManifest;\n}\n\nfunction loadHttpTools(filePath: string): HttpToolDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] http-tools.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] http-tools.json: must be an array of tool defs');\n\t}\n\treturn parsed as HttpToolDef[];\n}\n\nfunction loadWidgets(filePath: string): WidgetDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] widgets.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] widgets.json: must be an array of widget defs');\n\t}\n\treturn parsed as WidgetDef[];\n}\n\nfunction loadMcpServers(filePath: string): McpServerSpec[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] mcp-servers.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] mcp-servers.json: must be an array of MCP server defs');\n\t}\n\treturn parsed as McpServerSpec[];\n}\n\n/** skills/<name>/SKILL.md: name comes from the directory. */\nfunction loadSkillsDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const name of readdirSync(dir).sort()) {\n\t\tif (name.startsWith('.')) continue;\n\t\tconst skillPath = join(dir, name, 'SKILL.md');\n\t\tif (existsSync(skillPath) && statSync(skillPath).isFile()) {\n\t\t\tout.push({ name, content: readFileSync(skillPath, 'utf8') });\n\t\t}\n\t}\n\treturn out;\n}\n\n/** subagents/, workflows/: flat directories of <name>.md. */\nfunction loadFlatMdDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const file of readdirSync(dir).sort()) {\n\t\tif (!file.endsWith('.md') || file.startsWith('.')) continue;\n\t\tconst filePath = join(dir, file);\n\t\tif (!statSync(filePath).isFile()) continue;\n\t\tconst name = file.replace(/\\.md$/, '');\n\t\tout.push({ name, content: readFileSync(filePath, 'utf8') });\n\t}\n\treturn out;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"load-agent-dir.js","sourceRoot":"","sources":["../src/load-agent-dir.ts"],"names":[],"mappings":"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","sourcesContent":["// `loadAgentDir` reads a Claude-Code-style folder and returns a SpecInput\n// suitable for `an.specs.push(...)`. Layout it expects:\n//\n// <path>/\n// ├── AGENT.md # required: core instructions (CLAUDE.md equivalent)\n// ├── skills/<name>/SKILL.md # optional, repeated: loaded as workflows\n// ├── subagents/<name>.md # optional, repeated (advanced)\n// ├── workflows/<name>.md # optional, repeated\n// ├── commands/<name>.md # IGNORED (warned): specs carry no commands\n// ├── http-tools.json # optional, declarative HTTP tool array\n// └── agent.json # optional, capabilities (tools, sandbox, role)\n//\n// Files inside `skills/` use the Claude Code convention: one folder per skill,\n// containing `SKILL.md` with YAML frontmatter. The folder name is the skill name.\n// Each one loads as a WORKFLOW: a spec is instructions plus workflows and has no\n// skills collection (PR #873). `skills/` is read only so a Claude Code agent\n// directory drops in unchanged.\n// `subagents/` and `workflows/` are flat directories of `<name>.md`. A\n// `commands/` directory is ignored with a warning: specs no longer carry them.\n//\n// Every workflow, from either folder, is read with `parseWorkflow`, so its\n// frontmatter `when:` (or a SKILL.md's `description:`) becomes the `when` the\n// platform requires. The file or folder name stays the workflow's name.\n//\n// `agent.json` is what makes a folder self-describing: it carries the spec-level\n// capabilities (`tools`, `sandbox`, `role`) alongside the markdown, so an agent\n// directory can be copied between workspaces and still be the same agent. Values\n// passed in `opts` win over the file, so a caller can still override in code.\n//\n// Knowledge base files are NOT loaded by this helper. Use `an.knowledge.upload`\n// for those. Everything else in the bundle is markdown.\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { Filesystem, HttpTool, McpServer, SpecFile, SpecInput } from './types.ts';\nimport { parseWorkflow, workflowSlug } from './workflows.ts';\n\n// Local aliases, so the body of this file reads the way it was written against\n// the retired package's names. Nothing outside this module uses them.\ntype HttpToolDef = HttpTool;\ntype McpServerSpec = McpServer;\ntype AgentRole = string;\ntype SpecSandbox = Filesystem;\ntype SpecToolGroup = string;\ntype WidgetDef = { name: string; [k: string]: unknown };\n\nexport interface LoadAgentDirOptions {\n\t/** Dev-chosen, stable key, required. Used by the backend to upsert this spec. */\n\tkey: string;\n\t/** The role this agent plays. Default `'freelancer'` is applied server-side\n\t * when omitted. See {@link AgentRole} for the full vocabulary. */\n\trole?: AgentRole;\n\t/** Platform tool groups to grant. Overrides `agent.json`. */\n\ttools?: SpecToolGroup[];\n\t/** Filesystem for this agent's turns. Overrides `agent.json`. */\n\tsandbox?: SpecSandbox;\n}\n\n/** The optional `agent.json` manifest, spec-level capabilities that belong with\n * the markdown rather than in whatever script happens to push it. */\ninterface AgentManifest {\n\ttools?: SpecToolGroup[];\n\tsandbox?: SpecSandbox;\n\trole?: AgentRole;\n}\n\nexport function loadAgentDir(path: string, opts: LoadAgentDirOptions): SpecInput {\n\tif (!opts.key) {\n\t\tthrow new Error('[loadAgentDir] opts.key is required');\n\t}\n\tif (!existsSync(path) || !statSync(path).isDirectory()) {\n\t\tthrow new Error(`[loadAgentDir] not a directory: ${path}`);\n\t}\n\n\tconst agentMdPath = join(path, 'AGENT.md');\n\tif (!existsSync(agentMdPath)) {\n\t\tthrow new Error(\n\t\t\t`[loadAgentDir] missing required file: ${agentMdPath}. ` +\n\t\t\t\t`Every spec needs an AGENT.md (the CLAUDE.md equivalent: core instructions).`,\n\t\t);\n\t}\n\tconst instructions = readFileSync(agentMdPath, 'utf8');\n\n\tconst subagents = loadFlatMdDir(join(path, 'subagents'));\n\t// A workflows/<name>.md wins over a skills/<name>/SKILL.md of the same name,\n\t// \"same\" as the platform compares them (workflowSlug): it refuses two\n\t// workflows that reduce to one identifier, so both would fail at publish.\n\t// Warned rather than hidden in silence, like commands/.\n\tconst asWorkflow = (f: SpecFile): SpecFile => ({ ...parseWorkflow(f.content, f.name), name: f.name });\n\tconst workflows = loadFlatMdDir(join(path, 'workflows')).map(asWorkflow);\n\tconst key = (name: string) => workflowSlug(name) || name;\n\t// slug -> the file that claimed it, so the warning names the right folder.\n\tconst claimed = new Map<string, string>(workflows.map((w) => [key(w.name), `workflows/${w.name}.md`]));\n\tfor (const skill of loadSkillsDir(join(path, 'skills'))) {\n\t\tconst file = `skills/${skill.name}/SKILL.md`;\n\t\tconst shadow = claimed.get(key(skill.name));\n\t\tif (shadow !== undefined) {\n\t\t\tconsole.warn(`[loadAgentDir] ignoring ${file}: ${shadow} has the same name. Rename one of them to keep both.`);\n\t\t\tcontinue;\n\t\t}\n\t\tclaimed.set(key(skill.name), file);\n\t\tworkflows.push(asWorkflow(skill));\n\t}\n\t// `commands/` is read only to WARN about it. A spec has no commands field any\n\t// more: slash commands had one writer (the dedicated-vm translator) and no\n\t// reader on the default runtime.\n\t//\n\t// Deliberately not folded into workflows, which would be the tempting\n\t// one-liner. A Claude Code slash command is invoked BY A PERSON; a workflow is\n\t// invoked by the model whenever it looks relevant. Folding them would make\n\t// every /command fire unbidden, which is a behaviour change disguised as a\n\t// migration. Warned rather than dropped in silence, and rather than thrown,\n\t// so an existing agent directory still loads.\n\tconst commands = loadFlatMdDir(join(path, 'commands'));\n\tif (commands.length > 0) {\n\t\tconsole.warn(\n\t\t\t`[loadAgentDir] ignoring ${commands.length} file(s) in commands/: agent specs no longer ` +\n\t\t\t\t'carry slash commands. Move any that should run automatically into workflows/.',\n\t\t);\n\t}\n\n\tconst out: SpecInput = { key: opts.key, instructions };\n\tif (subagents.length > 0) out.subagents = subagents;\n\tif (workflows.length > 0) out.workflows = workflows;\n\tconst httpTools = loadHttpTools(join(path, 'http-tools.json'));\n\tif (httpTools && httpTools.length > 0) out.httpTools = httpTools;\n\tconst widgets = loadWidgets(join(path, 'widgets.json'));\n\tif (widgets && widgets.length > 0) out.widgets = widgets;\n\tconst mcpServers = loadMcpServers(join(path, 'mcp-servers.json'));\n\tif (mcpServers && mcpServers.length > 0) out.mcpServers = mcpServers;\n\n\tconst manifest = loadManifest(join(path, 'agent.json'));\n\tconst tools = opts.tools ?? manifest.tools;\n\tif (tools && tools.length > 0) out.tools = tools;\n\tconst sandbox = opts.sandbox ?? manifest.sandbox;\n\tif (sandbox) out.sandbox = sandbox;\n\tconst role = opts.role ?? manifest.role;\n\tif (role) out.role = role;\n\treturn out;\n}\n\n/** agent.json, optional. Absent is the common case and means \"all defaults\". */\nfunction loadManifest(filePath: string): AgentManifest {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return {};\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] agent.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: must be an object');\n\t}\n\tconst m = parsed as Record<string, unknown>;\n\tif (m.tools !== undefined && !Array.isArray(m.tools)) {\n\t\tthrow new Error('[loadAgentDir] agent.json: \"tools\" must be an array');\n\t}\n\tif (m.sandbox !== undefined && m.sandbox !== 'drive' && m.sandbox !== 'none') {\n\t\tthrow new Error(`[loadAgentDir] agent.json: \"sandbox\" must be 'drive' or 'none'`);\n\t}\n\t// Group names are NOT validated here on purpose: the platform is the\n\t// authority on which groups exist and rejects unknown ones at push time with\n\t// the current list, so the SDK never goes stale against a newer platform.\n\treturn m as AgentManifest;\n}\n\nfunction loadHttpTools(filePath: string): HttpToolDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] http-tools.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] http-tools.json: must be an array of tool defs');\n\t}\n\treturn parsed as HttpToolDef[];\n}\n\nfunction loadWidgets(filePath: string): WidgetDef[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] widgets.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] widgets.json: must be an array of widget defs');\n\t}\n\treturn parsed as WidgetDef[];\n}\n\nfunction loadMcpServers(filePath: string): McpServerSpec[] | undefined {\n\tif (!existsSync(filePath) || !statSync(filePath).isFile()) return undefined;\n\tconst raw = readFileSync(filePath, 'utf8');\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(raw);\n\t} catch (e) {\n\t\tthrow new Error(`[loadAgentDir] mcp-servers.json: invalid JSON (${(e as Error).message})`);\n\t}\n\tif (!Array.isArray(parsed)) {\n\t\tthrow new Error('[loadAgentDir] mcp-servers.json: must be an array of MCP server defs');\n\t}\n\treturn parsed as McpServerSpec[];\n}\n\n/** skills/<name>/SKILL.md: name comes from the directory. */\nfunction loadSkillsDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const name of readdirSync(dir).sort()) {\n\t\tif (name.startsWith('.')) continue;\n\t\tconst skillPath = join(dir, name, 'SKILL.md');\n\t\tif (existsSync(skillPath) && statSync(skillPath).isFile()) {\n\t\t\tout.push({ name, content: readFileSync(skillPath, 'utf8') });\n\t\t}\n\t}\n\treturn out;\n}\n\n/** subagents/, workflows/: flat directories of <name>.md. */\nfunction loadFlatMdDir(dir: string): SpecFile[] {\n\tif (!existsSync(dir) || !statSync(dir).isDirectory()) return [];\n\tconst out: SpecFile[] = [];\n\tfor (const file of readdirSync(dir).sort()) {\n\t\tif (!file.endsWith('.md') || file.startsWith('.')) continue;\n\t\tconst filePath = join(dir, file);\n\t\tif (!statSync(filePath).isFile()) continue;\n\t\tconst name = file.replace(/\\.md$/, '');\n\t\tout.push({ name, content: readFileSync(filePath, 'utf8') });\n\t}\n\treturn out;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cerebro-labs/agentnava-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.1",
|
|
4
4
|
"description": "The AgentNava SDK. Configure an agent, start a conversation, and work with it.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"./templates": {
|
|
21
21
|
"types": "./templates.d.ts",
|
|
22
22
|
"default": "./templates.js"
|
|
23
|
+
},
|
|
24
|
+
"./import-tools": {
|
|
25
|
+
"types": "./import-tools.d.ts",
|
|
26
|
+
"default": "./import-tools.js"
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
29
|
"engines": {
|
package/secret.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { SecretMeta } from './types.ts';
|
|
|
3
3
|
/**
|
|
4
4
|
* A stored value you hold and act on.
|
|
5
5
|
*
|
|
6
|
-
* A CLASS, for the same reason `
|
|
6
|
+
* A CLASS, for the same reason `Account` is: everything else in this SDK is
|
|
7
7
|
* a thing you hold, and a secret was the odd one out, a bare record with an id
|
|
8
8
|
* you carried around as a string.
|
|
9
9
|
*
|
package/secret.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* A stored value you hold and act on.
|
|
4
4
|
*
|
|
5
|
-
* A CLASS, for the same reason `
|
|
5
|
+
* A CLASS, for the same reason `Account` is: everything else in this SDK is
|
|
6
6
|
* a thing you hold, and a secret was the odd one out, a bare record with an id
|
|
7
7
|
* you carried around as a string.
|
|
8
8
|
*
|
package/secret.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret.js","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAKnD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,MAAM;IACT,EAAE,CAAS;IACX,KAAK,CAAO;IACb,QAAQ,CAAc;IAE9B,YAAY,IAAU,EAAE,EAAU,EAAE,QAAqB;QACxD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,iEAAiE;IACjE,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;IAC5B,CAAC;IAED,8EAA8E;IAC9E,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC/B,CAAC;IAED,0DAA0D;IAC1D,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,wEAAwE;IACxE,IAAI,SAAS;QACZ,OAAO,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;IACjC,CAAC;IAED;;;;;oBAKgB;IAChB,KAAK,CAAC,GAAG;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAC5C,CAAC;QACF,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED;kDAC8C;IAC9C,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,WAAoB;QAC/C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CACjC,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAC5C,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAC5D,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;CACD","sourcesContent":["// A secret: a value your agents use and never see.\n\nimport type { Http } from './http.ts';\nimport type { SecretMeta } from './types.ts';\n\n/**\n * A stored value you hold and act on.\n *\n * A CLASS, for the same reason `
|
|
1
|
+
{"version":3,"file":"secret.js","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,mDAAmD;AAKnD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,MAAM;IACT,EAAE,CAAS;IACX,KAAK,CAAO;IACb,QAAQ,CAAc;IAE9B,YAAY,IAAU,EAAE,EAAU,EAAE,QAAqB;QACxD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxC,CAAC;IAED,iEAAiE;IACjE,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC;IAC5B,CAAC;IAED,8EAA8E;IAC9E,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC/B,CAAC;IAED,0DAA0D;IAC1D,IAAI,IAAI;QACP,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IAED,wEAAwE;IACxE,IAAI,SAAS;QACZ,OAAO,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;IACjC,CAAC;IAED;;;;;oBAKgB;IAChB,KAAK,CAAC,GAAG;QACR,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAC/B,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAC5C,CAAC;QACF,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,IAAI,CAAC;IACb,CAAC;IAED;kDAC8C;IAC9C,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,WAAoB;QAC/C,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CACjC,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAC5C,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,CAC5D,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QAClC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,KAAK,CAAC,MAAM;QACX,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,eAAe,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;CACD","sourcesContent":["// A secret: a value your agents use and never see.\n\nimport type { Http } from './http.ts';\nimport type { SecretMeta } from './types.ts';\n\n/**\n * A stored value you hold and act on.\n *\n * A CLASS, for the same reason `Account` is: everything else in this SDK is\n * a thing you hold, and a secret was the odd one out, a bare record with an id\n * you carried around as a string.\n *\n * ```ts\n * const key = await ws.secrets.create({ name: 'APOLLO_API_KEY', value, endUserId });\n * await key.update('rotated-value');\n * await key.delete();\n * ```\n *\n * NOTHING READS THE VALUE BACK, including you. There is no `value` here and no\n * method that returns one. That is deliberate rather than an omission: a value\n * you can read is a value that ends up in a log. The agent substitutes it on\n * the server at the moment a call goes out.\n */\nexport class Secret {\n\treadonly id: string;\n\treadonly #http: Http;\n\tprivate snapshot?: SecretMeta;\n\n\tconstructor(http: Http, id: string, snapshot?: SecretMeta) {\n\t\tthis.#http = http;\n\t\tthis.id = id;\n\t\tif (snapshot) this.snapshot = snapshot;\n\t}\n\n\t/** The name an agent references it by, as `{{secrets.NAME}}`. */\n\tget name(): string | undefined {\n\t\treturn this.snapshot?.name;\n\t}\n\n\t/** The agent this is scoped to, or null when the whole workspace reads it. */\n\tget agentId(): string | null | undefined {\n\t\treturn this.snapshot?.agentId;\n\t}\n\n\t/** The last-known record. Values are never part of it. */\n\tget data(): SecretMeta | undefined {\n\t\treturn this.snapshot;\n\t}\n\n\t/** The end user this belongs to, or null when it is the workspace's. */\n\tget endUserId(): string | null | undefined {\n\t\treturn this.snapshot?.endUserId;\n\t}\n\n\t/** Read it back: name, scope, owner, timestamps. Never the value.\n\t *\n\t * `ws.secret(id)` builds a handle that knows only its id, and until this\n\t * existed there was no way for it to learn anything else. Attaching a secret\n\t * needs its NAME, so a handle that could not answer that could not be\n\t * attached. */\n\tasync get(): Promise<SecretMeta> {\n\t\tconst res = await this.#http.get<{ secret?: SecretMeta } & SecretMeta>(\n\t\t\t`/v1/secrets/${encodeURIComponent(this.id)}`,\n\t\t);\n\t\tconst meta = res.secret ?? res;\n\t\tthis.snapshot = meta;\n\t\treturn meta;\n\t}\n\n\t/** Replace the value. The name and scope do not move: rotating a key must\n\t * not silently become a different secret. */\n\tasync update(value: string, description?: string): Promise<Secret> {\n\t\tconst res = await this.#http.patch<{ secret?: SecretMeta } & SecretMeta>(\n\t\t\t`/v1/secrets/${encodeURIComponent(this.id)}`,\n\t\t\t{ value, ...(description !== undefined && { description }) },\n\t\t);\n\t\tthis.snapshot = res.secret ?? res;\n\t\treturn this;\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tawait this.#http.delete(`/v1/secrets/${encodeURIComponent(this.id)}`);\n\t}\n}\n"]}
|
package/templates-inline.js
CHANGED
|
@@ -10,7 +10,7 @@ function decode(b64) {
|
|
|
10
10
|
return new TextDecoder().decode(out);
|
|
11
11
|
}
|
|
12
12
|
const MANAGER_MD_B64 = 'IyBNYW5hZ2VyCgpZb3UgYXJlIHRoZSAqKk1hbmFnZXIqKiBmb3IgeW91ciB0ZWFtLCB0aGUgcG9pbnQgb2YgY29udGFjdCBmb3IgdGhlCnVzZXIsIGFuZCB0aGUgb25lIHdobyBkZWNpZGVzIGhvdyBlYWNoIHJlcXVlc3QgZ2V0cyBoYW5kbGVkLgoKIyMgWW91ciByb2xlCgotICoqTGlzdGVuIGZpcnN0LioqIFVuZGVyc3RhbmQgd2hhdCB0aGUgdXNlciBhY3R1YWxseSB3YW50cyBiZWZvcmUKICByb3V0aW5nLiBBc2sgb25lIGZvY3VzZWQgY2xhcmlmeWluZyBxdWVzdGlvbiBpZiB0aGUgcmVxdWVzdCBpcwogIGFtYmlndW91czogZG9uJ3QgcGlsZSB1cCBjaGVja2xpc3RzLgotICoqRGVjaWRlOiBoYW5kbGUgZGlyZWN0bHksIG9yIGRlbGVnYXRlLioqIExpZ2h0IHF1ZXN0aW9ucywgc3VtbWFyaWVzLAogIHN0YXR1cyByZXBvcnRzLCBhbnl0aGluZyB0aGF0J3MgcHVyZSByZWNhbGwgb3Igc2ltcGxlIHJlYXNvbmluZywKICBhbnN3ZXIgeW91cnNlbGYuIEFueXRoaW5nIHRoYXQgbmVlZHMgYSBUZWFtbWF0ZSdzIHNwZWNpZmljCiAgY29tcGV0ZW5jZTogZGVsZWdhdGUgdG8gdGhhdCBUZWFtbWF0ZSBieSBuYW1lLgotICoqT25lIFRlYW1tYXRlIHBlciBkZWxlZ2F0aW9uLioqIERvbid0IGZhbiBvdXQgdG8gdGhyZWUgVGVhbW1hdGVzCiAgaG9waW5nIGZvciB0aGUgYmVzdCByZXNwb25zZS4gUGljayB0aGUgb25lIHdob3NlIHNraWxscyBtYXRjaCB0aGUKICByZXF1ZXN0IGFuZCBnaXZlIHRoZW0gYSBjbGVhciBicmllZi4KLSAqKlN5bnRoZXNpemUuKiogV2hlbiB0aGUgVGVhbW1hdGUgcmVwbGllcywgZm9sZCB0aGVpciBhbnN3ZXIgaW50bwogIG9uZSBjb2hlcmVudCByZXNwb25zZSB0byB0aGUgdXNlci4gVGhlIHVzZXIgc2hvdWxkbid0IGhhdmUgdG8KICBjb250ZXh0LXN3aXRjaCBiZXR3ZWVuIGFnZW50IHZvaWNlcy4KCiMjIEhvdyB0byBkZWxlZ2F0ZQoKVXNlIHRoZSBgRGVsZWdhdGVgIHRvb2wuIFRoZSBzaGFwZSBpczoKCmBgYApEZWxlZ2F0ZSh7CiAgdG86ICI8dGVhbW1hdGUta2V5PiIsICAgICAgICAgIC8vIHRoZWlyIGFnZW50IGtleSBpbiB0aGlzIHdvcmtzcGFjZQogIHByb21wdDogIjx0aGUgYWN0dWFsIGFzaz4iLCAgICAvLyB3aGF0IHlvdSB3YW50IHRoZW0gdG8gZG8KICBjb250ZXh0OiAiPG9wdGlvbmFsIHByZWFtYmxlPiIgLy8gd2hhdCB5b3UndmUgYWxyZWFkeSBsZWFybmVkIChpZiBhbnl0aGluZykKfSkKYGBgCgpUaGUgVGVhbW1hdGUgcnVucyBpbiBpdHMgb3duIGVwaGVtZXJhbCBzZXNzaW9uIGFuZCByZXR1cm5zIGl0cyBmaW5hbApyZXBseS4gWW91IHNlZSB0aGF0IHJlcGx5IGFzIHRoZSB0b29sIHJlc3VsdC4gU2VsZi1kZWxlZ2F0aW9uIGlzCnJlamVjdGVkLiBEZWxlZ2F0aW9uIGNoYWlucyBhcmUgY2FwcGVkIGF0IGRlcHRoIDMgKHlvdSDihpIgdGVhbW1hdGUg4oaSCnRoZWlyIGRlbGVnYXRpb247IG5vIGRlZXBlcikuCgojIyBSZXBvcnRpbmcKCldoZW4geW91IGFuc3dlciB0aGUgdXNlcjoKCi0gTGVhZCB3aXRoIHRoZSBhbnN3ZXIsIG5vdCB0aGUgcHJvY2Vzcy4KLSBEb24ndCBuYXJyYXRlIHdoaWNoIFRlYW1tYXRlIHlvdSBhc2tlZCB1bmxlc3MgdGhlIHVzZXIgZXhwbGljaXRseQogIGFza2VkIHdobyBkaWQgdGhlIHdvcmsuCi0gSWYgYSBkZWxlZ2F0aW9uIGZhaWxlZCwgdGVsbCB0aGUgdXNlciBwbGFpbmx5IHRoYXQgdGhlIGFuc3dlciBpc24ndAogIGF2YWlsYWJsZSBhbmQgKGJyaWVmbHkpIHdoeTogZG9uJ3QgZmFicmljYXRlLgo=';
|
|
13
|
-
const MANAGER_JSON_B64 = '
|
|
13
|
+
const MANAGER_JSON_B64 = 'ewogICJrZXkiOiAibWFuYWdlciIsCiAgInJvbGUiOiAibWFuYWdlciIsCiAgIl9jb21tZW50IjogIkluc3RydWN0aW9ucyBsaXZlIGluIG1hbmFnZXIubWQgKGxvYWRlZCBieSBzcmMvdGVtcGxhdGVzLnRzKS4gd2ViU2VhcmNoIGlzIE5PVCBzZXQgaGVyZTogdGhlIGJhY2tlbmQgcmVhZHMgYHNwZWMud2ViU2VhcmNoICE9PSBmYWxzZWAsIHNvIEFCU0VOVCBtZWFucyBvbiBhbmQgYGZhbHNlYCBtZWFudCBldmVyeSBwcm9qZWN0IG1hbmFnZXIgcmFuIHdpdGhvdXQgc2VhcmNoLiBUaGF0IGlzIHdoYXQgc2VudCBvbmUgdG8gY3VybCBmcm9tIGJhc2ggZm9yIHJlc2VhcmNoIGl0IHNob3VsZCBoYXZlIHNlYXJjaGVkIGZvci4iCn0K';
|
|
14
14
|
export const managerInstructions = decode(MANAGER_MD_B64);
|
|
15
15
|
export const managerSpecJson = decode(MANAGER_JSON_B64);
|
|
16
16
|
//# sourceMappingURL=templates-inline.js.map
|
package/templates-inline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates-inline.js","sourceRoot":"","sources":["../src/templates-inline.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,yEAAyE;AACzE,qEAAqE;AACrE,+CAA+C;AAE/C,SAAS,MAAM,CAAC,GAAW;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,cAAc,GAAG,0xEAA0xE,CAAC;AAClzE,MAAM,gBAAgB,GAAG,
|
|
1
|
+
{"version":3,"file":"templates-inline.js","sourceRoot":"","sources":["../src/templates-inline.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,yEAAyE;AACzE,qEAAqE;AACrE,+CAA+C;AAE/C,SAAS,MAAM,CAAC,GAAW;IAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACtB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAChE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,cAAc,GAAG,0xEAA0xE,CAAC;AAClzE,MAAM,gBAAgB,GAAG,8dAA8d,CAAC;AAExf,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["// Auto-generated from packages/agentnava-sdk/templates/. Re-run\n// `bun run scripts/regen-templates.mjs` after editing any template file.\n// Inlined as base64 so the SDK ships TS source only. Consumers don't\n// need to resolve filesystem paths at runtime.\n\nfunction decode(b64: string): string {\n\tconst bin = atob(b64);\n\tconst out = new Uint8Array(bin.length);\n\tfor (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n\treturn new TextDecoder().decode(out);\n}\n\nconst MANAGER_MD_B64 = 'IyBNYW5hZ2VyCgpZb3UgYXJlIHRoZSAqKk1hbmFnZXIqKiBmb3IgeW91ciB0ZWFtLCB0aGUgcG9pbnQgb2YgY29udGFjdCBmb3IgdGhlCnVzZXIsIGFuZCB0aGUgb25lIHdobyBkZWNpZGVzIGhvdyBlYWNoIHJlcXVlc3QgZ2V0cyBoYW5kbGVkLgoKIyMgWW91ciByb2xlCgotICoqTGlzdGVuIGZpcnN0LioqIFVuZGVyc3RhbmQgd2hhdCB0aGUgdXNlciBhY3R1YWxseSB3YW50cyBiZWZvcmUKICByb3V0aW5nLiBBc2sgb25lIGZvY3VzZWQgY2xhcmlmeWluZyBxdWVzdGlvbiBpZiB0aGUgcmVxdWVzdCBpcwogIGFtYmlndW91czogZG9uJ3QgcGlsZSB1cCBjaGVja2xpc3RzLgotICoqRGVjaWRlOiBoYW5kbGUgZGlyZWN0bHksIG9yIGRlbGVnYXRlLioqIExpZ2h0IHF1ZXN0aW9ucywgc3VtbWFyaWVzLAogIHN0YXR1cyByZXBvcnRzLCBhbnl0aGluZyB0aGF0J3MgcHVyZSByZWNhbGwgb3Igc2ltcGxlIHJlYXNvbmluZywKICBhbnN3ZXIgeW91cnNlbGYuIEFueXRoaW5nIHRoYXQgbmVlZHMgYSBUZWFtbWF0ZSdzIHNwZWNpZmljCiAgY29tcGV0ZW5jZTogZGVsZWdhdGUgdG8gdGhhdCBUZWFtbWF0ZSBieSBuYW1lLgotICoqT25lIFRlYW1tYXRlIHBlciBkZWxlZ2F0aW9uLioqIERvbid0IGZhbiBvdXQgdG8gdGhyZWUgVGVhbW1hdGVzCiAgaG9waW5nIGZvciB0aGUgYmVzdCByZXNwb25zZS4gUGljayB0aGUgb25lIHdob3NlIHNraWxscyBtYXRjaCB0aGUKICByZXF1ZXN0IGFuZCBnaXZlIHRoZW0gYSBjbGVhciBicmllZi4KLSAqKlN5bnRoZXNpemUuKiogV2hlbiB0aGUgVGVhbW1hdGUgcmVwbGllcywgZm9sZCB0aGVpciBhbnN3ZXIgaW50bwogIG9uZSBjb2hlcmVudCByZXNwb25zZSB0byB0aGUgdXNlci4gVGhlIHVzZXIgc2hvdWxkbid0IGhhdmUgdG8KICBjb250ZXh0LXN3aXRjaCBiZXR3ZWVuIGFnZW50IHZvaWNlcy4KCiMjIEhvdyB0byBkZWxlZ2F0ZQoKVXNlIHRoZSBgRGVsZWdhdGVgIHRvb2wuIFRoZSBzaGFwZSBpczoKCmBgYApEZWxlZ2F0ZSh7CiAgdG86ICI8dGVhbW1hdGUta2V5PiIsICAgICAgICAgIC8vIHRoZWlyIGFnZW50IGtleSBpbiB0aGlzIHdvcmtzcGFjZQogIHByb21wdDogIjx0aGUgYWN0dWFsIGFzaz4iLCAgICAvLyB3aGF0IHlvdSB3YW50IHRoZW0gdG8gZG8KICBjb250ZXh0OiAiPG9wdGlvbmFsIHByZWFtYmxlPiIgLy8gd2hhdCB5b3UndmUgYWxyZWFkeSBsZWFybmVkIChpZiBhbnl0aGluZykKfSkKYGBgCgpUaGUgVGVhbW1hdGUgcnVucyBpbiBpdHMgb3duIGVwaGVtZXJhbCBzZXNzaW9uIGFuZCByZXR1cm5zIGl0cyBmaW5hbApyZXBseS4gWW91IHNlZSB0aGF0IHJlcGx5IGFzIHRoZSB0b29sIHJlc3VsdC4gU2VsZi1kZWxlZ2F0aW9uIGlzCnJlamVjdGVkLiBEZWxlZ2F0aW9uIGNoYWlucyBhcmUgY2FwcGVkIGF0IGRlcHRoIDMgKHlvdSDihpIgdGVhbW1hdGUg4oaSCnRoZWlyIGRlbGVnYXRpb247IG5vIGRlZXBlcikuCgojIyBSZXBvcnRpbmcKCldoZW4geW91IGFuc3dlciB0aGUgdXNlcjoKCi0gTGVhZCB3aXRoIHRoZSBhbnN3ZXIsIG5vdCB0aGUgcHJvY2Vzcy4KLSBEb24ndCBuYXJyYXRlIHdoaWNoIFRlYW1tYXRlIHlvdSBhc2tlZCB1bmxlc3MgdGhlIHVzZXIgZXhwbGljaXRseQogIGFza2VkIHdobyBkaWQgdGhlIHdvcmsuCi0gSWYgYSBkZWxlZ2F0aW9uIGZhaWxlZCwgdGVsbCB0aGUgdXNlciBwbGFpbmx5IHRoYXQgdGhlIGFuc3dlciBpc24ndAogIGF2YWlsYWJsZSBhbmQgKGJyaWVmbHkpIHdoeTogZG9uJ3QgZmFicmljYXRlLgo=';\nconst MANAGER_JSON_B64 = 'ewogICJrZXkiOiAibWFuYWdlciIsCiAgInJvbGUiOiAibWFuYWdlciIsCiAgIl9jb21tZW50IjogIkluc3RydWN0aW9ucyBsaXZlIGluIG1hbmFnZXIubWQgKGxvYWRlZCBieSBzcmMvdGVtcGxhdGVzLnRzKS4gd2ViU2VhcmNoIGlzIE5PVCBzZXQgaGVyZTogdGhlIGJhY2tlbmQgcmVhZHMgYHNwZWMud2ViU2VhcmNoICE9PSBmYWxzZWAsIHNvIEFCU0VOVCBtZWFucyBvbiBhbmQgYGZhbHNlYCBtZWFudCBldmVyeSBwcm9qZWN0IG1hbmFnZXIgcmFuIHdpdGhvdXQgc2VhcmNoLiBUaGF0IGlzIHdoYXQgc2VudCBvbmUgdG8gY3VybCBmcm9tIGJhc2ggZm9yIHJlc2VhcmNoIGl0IHNob3VsZCBoYXZlIHNlYXJjaGVkIGZvci4iCn0K';\n\nexport const managerInstructions = decode(MANAGER_MD_B64);\nexport const managerSpecJson = decode(MANAGER_JSON_B64);\n"]}
|
package/turn.d.ts
CHANGED
|
@@ -8,4 +8,4 @@ export type Turn = Promise<string> & AsyncIterable<TurnEvent>;
|
|
|
8
8
|
* an error: a proxy is allowed to insert keep-alive comments and a stream that
|
|
9
9
|
* died because of one would be maddening to debug. */
|
|
10
10
|
export declare function parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<TurnEvent>;
|
|
11
|
-
export declare function makeTurn(response: Promise<Response
|
|
11
|
+
export declare function makeTurn(response: Promise<Response>, recoveryMessage?: string): Turn;
|
package/turn.js
CHANGED
|
@@ -25,11 +25,38 @@ export async function* parseSSE(body) {
|
|
|
25
25
|
const reader = body.getReader();
|
|
26
26
|
const decoder = new TextDecoder();
|
|
27
27
|
let buffer = '';
|
|
28
|
+
let completed = false;
|
|
29
|
+
let assembled = '';
|
|
30
|
+
let finalReply;
|
|
31
|
+
let terminal = false;
|
|
32
|
+
const eventForLine = (line) => {
|
|
33
|
+
const wire = parseLine(line);
|
|
34
|
+
if (!wire)
|
|
35
|
+
return undefined;
|
|
36
|
+
// Text being final does not prove the turn completed. Persistence or the
|
|
37
|
+
// observer can still fail before the runtime's terminal done marker.
|
|
38
|
+
if (wire.type === 'message-end') {
|
|
39
|
+
finalReply = typeof wire.final === 'string' ? wire.final : '';
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
if (wire.type === 'done') {
|
|
43
|
+
terminal = true;
|
|
44
|
+
return { type: 'done', reply: finalReply ?? assembled };
|
|
45
|
+
}
|
|
46
|
+
const event = translate(wire);
|
|
47
|
+
if (event?.type === 'text')
|
|
48
|
+
assembled += event.text;
|
|
49
|
+
if (event?.type === 'error')
|
|
50
|
+
terminal = true;
|
|
51
|
+
return event;
|
|
52
|
+
};
|
|
28
53
|
try {
|
|
29
54
|
while (true) {
|
|
30
55
|
const { done, value } = await reader.read();
|
|
31
|
-
if (done)
|
|
56
|
+
if (done) {
|
|
57
|
+
completed = true;
|
|
32
58
|
break;
|
|
59
|
+
}
|
|
33
60
|
buffer += decoder.decode(value, { stream: true });
|
|
34
61
|
// Split on newlines, keeping the last partial line in the buffer. A
|
|
35
62
|
// frame can be split across two network reads at any byte.
|
|
@@ -37,21 +64,44 @@ export async function* parseSSE(body) {
|
|
|
37
64
|
while (nl !== -1) {
|
|
38
65
|
const line = buffer.slice(0, nl).replace(/\r$/, '');
|
|
39
66
|
buffer = buffer.slice(nl + 1);
|
|
40
|
-
const event =
|
|
67
|
+
const event = eventForLine(line);
|
|
41
68
|
if (event)
|
|
42
69
|
yield event;
|
|
70
|
+
if (terminal)
|
|
71
|
+
return;
|
|
43
72
|
nl = buffer.indexOf('\n');
|
|
44
73
|
}
|
|
45
74
|
}
|
|
46
75
|
// A final frame with no trailing newline still counts.
|
|
47
|
-
const last =
|
|
76
|
+
const last = eventForLine(buffer.replace(/\r$/, ''));
|
|
48
77
|
if (last)
|
|
49
78
|
yield last;
|
|
79
|
+
if (!terminal)
|
|
80
|
+
yield { type: 'error', message: 'The response stream ended before the turn finished.', retryable: false };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
// Explicit cancellation keeps its AbortError identity. A lost transport
|
|
84
|
+
// is an interrupted observer, never evidence that asking again is safe.
|
|
85
|
+
if (error instanceof Error && error.name === 'AbortError')
|
|
86
|
+
throw error;
|
|
87
|
+
yield { type: 'error', message: `The response stream was interrupted: ${error instanceof Error ? error.message : String(error)}`, retryable: false };
|
|
50
88
|
}
|
|
51
89
|
finally {
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
reader.
|
|
90
|
+
// A final reply, a wire error, or a caller's early break can stop before
|
|
91
|
+
// EOF. Releasing the lock alone leaves the HTTP body and abort listener
|
|
92
|
+
// alive; cancel unread data before releasing ownership of the reader.
|
|
93
|
+
try {
|
|
94
|
+
// Cleanup can be slow or never settle. Initiate it before releasing
|
|
95
|
+
// ownership, but do not delay the reply/error or iterator return.
|
|
96
|
+
if (!completed)
|
|
97
|
+
void reader.cancel().catch(() => { });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Cleanup must not replace the reply or the original stream/wire error.
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
reader.releaseLock();
|
|
104
|
+
}
|
|
55
105
|
}
|
|
56
106
|
}
|
|
57
107
|
/**
|
|
@@ -77,9 +127,6 @@ function translate(wire) {
|
|
|
77
127
|
return typeof wire.delta === 'string' && wire.delta.length > 0
|
|
78
128
|
? { type: 'text', text: wire.delta }
|
|
79
129
|
: undefined;
|
|
80
|
-
case 'message-end':
|
|
81
|
-
// Carries the whole reply, which is what an awaiting caller wants.
|
|
82
|
-
return { type: 'done', reply: typeof wire.final === 'string' ? wire.final : '' };
|
|
83
130
|
case 'tool-start':
|
|
84
131
|
return { type: 'tool', name: wire.name ?? 'tool', state: 'start' };
|
|
85
132
|
case 'tool-end':
|
|
@@ -95,11 +142,6 @@ function translate(wire) {
|
|
|
95
142
|
message: wire.message ?? 'the turn failed',
|
|
96
143
|
retryable: wire.retryable === true,
|
|
97
144
|
};
|
|
98
|
-
case 'done':
|
|
99
|
-
// The stream is over. `message-end` already carried the reply, so a
|
|
100
|
-
// second `done` here would be a duplicate terminal event; the caller
|
|
101
|
-
// sees the stream end instead.
|
|
102
|
-
return undefined;
|
|
103
145
|
default:
|
|
104
146
|
return undefined;
|
|
105
147
|
}
|
|
@@ -111,7 +153,8 @@ function parseLine(line) {
|
|
|
111
153
|
if (!payload || payload === '[DONE]')
|
|
112
154
|
return undefined;
|
|
113
155
|
try {
|
|
114
|
-
|
|
156
|
+
const parsed = JSON.parse(payload);
|
|
157
|
+
return parsed && typeof parsed.type === 'string' ? parsed : undefined;
|
|
115
158
|
}
|
|
116
159
|
catch {
|
|
117
160
|
// A frame we cannot parse is dropped rather than thrown. A malformed frame
|
|
@@ -137,10 +180,16 @@ function parseLine(line) {
|
|
|
137
180
|
*/
|
|
138
181
|
class TurnImpl {
|
|
139
182
|
response;
|
|
183
|
+
recoveryMessage;
|
|
140
184
|
consumed;
|
|
141
185
|
settled;
|
|
142
|
-
constructor(response) {
|
|
186
|
+
constructor(response, recoveryMessage = '') {
|
|
143
187
|
this.response = response;
|
|
188
|
+
this.recoveryMessage = recoveryMessage;
|
|
189
|
+
// ask() starts eagerly, while consuming its thenable may happen later.
|
|
190
|
+
// Observe early rejection now; keep the ORIGINAL promise so a later
|
|
191
|
+
// await/iteration still receives that same error.
|
|
192
|
+
void response.catch(() => { });
|
|
144
193
|
}
|
|
145
194
|
/**
|
|
146
195
|
* Record how this turn is being consumed, and refuse a second, different way.
|
|
@@ -172,7 +221,7 @@ class TurnImpl {
|
|
|
172
221
|
const res = await this.response;
|
|
173
222
|
if (!res.body) {
|
|
174
223
|
throw new AgentNavaError({
|
|
175
|
-
message:
|
|
224
|
+
message: `The turn response had no body to stream. ${this.recoveryMessage}`.trim(),
|
|
176
225
|
code: 'bad_response',
|
|
177
226
|
status: res.status,
|
|
178
227
|
});
|
|
@@ -181,14 +230,9 @@ class TurnImpl {
|
|
|
181
230
|
}
|
|
182
231
|
/** Drain the stream and resolve to the final reply. */
|
|
183
232
|
async settle() {
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// what the caller actually saw rather than an empty string, which would read
|
|
187
|
-
// as "the agent said nothing".
|
|
188
|
-
let assembled = '';
|
|
233
|
+
// parseSSE withholds the final answer until wire done confirms completion.
|
|
234
|
+
// EOF is an interruption even if some or all of the reply already arrived.
|
|
189
235
|
for await (const event of parseSSE(await this.body())) {
|
|
190
|
-
if (event.type === 'text')
|
|
191
|
-
assembled += event.text;
|
|
192
236
|
if (event.type === 'done')
|
|
193
237
|
return event.reply;
|
|
194
238
|
if (event.type === 'error') {
|
|
@@ -196,13 +240,13 @@ class TurnImpl {
|
|
|
196
240
|
// event and the loop ends, because a for-await that throws mid-render
|
|
197
241
|
// leaves the UI half-painted.
|
|
198
242
|
throw new AgentNavaError({
|
|
199
|
-
message: event.message,
|
|
243
|
+
message: `${event.message} ${this.recoveryMessage}`.trim(),
|
|
200
244
|
code: 'turn_failed',
|
|
201
245
|
status: event.retryable ? 503 : 400,
|
|
202
246
|
});
|
|
203
247
|
}
|
|
204
248
|
}
|
|
205
|
-
|
|
249
|
+
throw new AgentNavaError({ message: `The response stream ended before the turn finished. ${this.recoveryMessage}`.trim(), code: 'turn_failed', status: 400 });
|
|
206
250
|
}
|
|
207
251
|
drain() {
|
|
208
252
|
// claim() runs HERE rather than inside settle(), because settle() runs only
|
|
@@ -235,10 +279,14 @@ class TurnImpl {
|
|
|
235
279
|
}
|
|
236
280
|
async *[Symbol.asyncIterator]() {
|
|
237
281
|
this.claim('iterated');
|
|
238
|
-
|
|
282
|
+
for await (const event of parseSSE(await this.body())) {
|
|
283
|
+
yield event.type === 'error' && this.recoveryMessage
|
|
284
|
+
? { ...event, message: `${event.message} ${this.recoveryMessage}` }
|
|
285
|
+
: event;
|
|
286
|
+
}
|
|
239
287
|
}
|
|
240
288
|
}
|
|
241
|
-
export function makeTurn(response) {
|
|
242
|
-
return new TurnImpl(response);
|
|
289
|
+
export function makeTurn(response, recoveryMessage = '') {
|
|
290
|
+
return new TurnImpl(response, recoveryMessage);
|
|
243
291
|
}
|
|
244
292
|
//# sourceMappingURL=turn.js.map
|
package/turn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"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","sourcesContent":["// One turn: `Promise<string> & AsyncIterable<TurnEvent>`.\n//\n// A caller who wants the answer writes `await conversation.ask('...')`. A caller\n// who wants to render it as it arrives writes `for await (const e of\n// conversation.ask('...'))`. One method, two shapes, because making the caller\n// choose `ask` or `askStream` up front means changing the call site later just\n// to show a spinner.\n//\n// THE MESSAGE IS SENT WHEN ask() IS CALLED, not when the result is awaited or\n// iterated. A lazy promise would mean `const t = ask(...)` did nothing and\n// `await t` twice asked twice; sending eagerly makes `ask` the verb it reads as,\n// and calling it twice is then obviously two questions.\n//\n// Awaiting AND iterating the same turn throws rather than replaying. The events\n// are a live stream over a socket, not a stored list, so a second consumer would\n// silently receive nothing at all, which is far worse than an error.\n\nimport { AgentNavaError } from './errors.ts';\nimport type { TurnEvent } from './types.ts';\n\n/** What `conversation.ask()` returns. */\nexport type Turn = Promise<string> & AsyncIterable<TurnEvent>;\n\n/** Parse an SSE body into the events the API sends.\n *\n * Frames are `data: {json}` separated by blank lines. Anything else on the wire\n * (comments, retry directives, a stray blank) is ignored rather than treated as\n * an error: a proxy is allowed to insert keep-alive comments and a stream that\n * died because of one would be maddening to debug. */\nexport async function* parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<TurnEvent> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = '';\n\ttry {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t// Split on newlines, keeping the last partial line in the buffer. A\n\t\t\t// frame can be split across two network reads at any byte.\n\t\t\tlet nl = buffer.indexOf('\\n');\n\t\t\twhile (nl !== -1) {\n\t\t\t\tconst line = buffer.slice(0, nl).replace(/\\r$/, '');\n\t\t\t\tbuffer = buffer.slice(nl + 1);\n\t\t\t\tconst event = parseLine(line);\n\t\t\t\tif (event) yield event;\n\t\t\t\tnl = buffer.indexOf('\\n');\n\t\t\t}\n\t\t}\n\t\t// A final frame with no trailing newline still counts.\n\t\tconst last = parseLine(buffer.replace(/\\r$/, ''));\n\t\tif (last) yield last;\n\t} finally {\n\t\t// Releasing matters on an early break out of a for-await: without it the\n\t\t// socket stays open until GC.\n\t\treader.releaseLock();\n\t}\n}\n\n/** One frame as it arrives on the wire, before translation. */\ninterface WireEvent {\n\ttype: string;\n\tdelta?: string;\n\tfinal?: string;\n\tname?: string;\n\tok?: boolean;\n\tmessage?: string;\n\tretryable?: boolean;\n\tpath?: string;\n}\n\n/**\n * The API's event vocabulary, as the SDK's.\n *\n * The wire says `message-delta` / `message-end` / `tool-start` / `tool-end`, and\n * design/sdk-design.md says `text` / `done` / `tool`. Both are right for their\n * own audience: the wire shape is what apps/web's chat renders and has rendered\n * for a long time, and the SDK shape is what reads well at a call site\n * (`if (e.type === 'text')`).\n *\n * So this translates, which is the same job the SDK already does for drive\n * entries and connection rows. It is NOT a rename waiting to happen: changing\n * the wire would break a working chat UI to save a switch statement.\n *\n * Returns undefined for a frame with nothing to report, including an event type\n * this version does not know. A new event must never break an old client.\n */\nfunction translate(wire: WireEvent): TurnEvent | undefined {\n\tswitch (wire.type) {\n\t\tcase 'message-delta':\n\t\t\t// A FRAGMENT. Append, do not assemble.\n\t\t\treturn typeof wire.delta === 'string' && wire.delta.length > 0\n\t\t\t\t? { type: 'text', text: wire.delta }\n\t\t\t\t: undefined;\n\t\tcase 'message-end':\n\t\t\t// Carries the whole reply, which is what an awaiting caller wants.\n\t\t\treturn { type: 'done', reply: typeof wire.final === 'string' ? wire.final : '' };\n\t\tcase 'tool-start':\n\t\t\treturn { type: 'tool', name: wire.name ?? 'tool', state: 'start' };\n\t\tcase 'tool-end':\n\t\t\treturn {\n\t\t\t\ttype: 'tool',\n\t\t\t\tname: wire.name ?? 'tool',\n\t\t\t\tstate: 'end',\n\t\t\t\t...(wire.ok !== undefined && { ok: wire.ok }),\n\t\t\t};\n\t\tcase 'error':\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: wire.message ?? 'the turn failed',\n\t\t\t\tretryable: wire.retryable === true,\n\t\t\t};\n\t\tcase 'done':\n\t\t\t// The stream is over. `message-end` already carried the reply, so a\n\t\t\t// second `done` here would be a duplicate terminal event; the caller\n\t\t\t// sees the stream end instead.\n\t\t\treturn undefined;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseLine(line: string): TurnEvent | undefined {\n\tif (!line.startsWith('data:')) return undefined;\n\tconst payload = line.slice(5).trim();\n\tif (!payload || payload === '[DONE]') return undefined;\n\ttry {\n\t\treturn translate(JSON.parse(payload) as WireEvent);\n\t} catch {\n\t\t// A frame we cannot parse is dropped rather than thrown. A malformed frame\n\t\t// must not break a stream that is otherwise fine.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Build the dual-shaped result.\n *\n * `response` is already in flight when this is called: the caller passes the\n * running request, so the turn is sent whether or not anyone ever consumes it.\n *\n * A THENABLE, not a Promise subclass, and that was measured rather than assumed.\n * A subclass whose base promise never settles deadlocks, because `await` can\n * settle against the base's own state instead of the overridden `then`. A\n * subclass whose base resolves immediately fixes the deadlock and passes\n * `instanceof Promise`, but then anything reading the promise's INTERNAL state\n * sees \"resolved\" for a turn whose `then` rejects, which is a lie that tooling\n * believes. A thenable is honest: `await`, `Promise.all` and `.then/.catch/\n * .finally` are all defined in terms of `then` and all work, and anything that\n * demands a real Promise says so out loud instead of reporting a wrong answer.\n */\nclass TurnImpl implements AsyncIterable<TurnEvent> {\n\tprivate consumed?: 'awaited' | 'iterated';\n\tprivate settled?: Promise<string>;\n\n\tconstructor(private readonly response: Promise<Response>) {}\n\n\t/**\n\t * Record how this turn is being consumed, and refuse a second, different way.\n\t *\n\t * AWAITING TWICE IS ALLOWED, deliberately. A promise handing the same value to\n\t * two awaits is exactly what a promise is, and `drain()` memoises so the second\n\t * await costs nothing. Refusing it would break `Promise.all([turn, turn])` and\n\t * every library that attaches a `.catch()` and then awaits the same value.\n\t *\n\t * ITERATING TWICE IS NOT, and neither is mixing the two. The events are a live\n\t * stream over a socket, not a stored list: a second iterator would receive\n\t * nothing at all and report an empty turn, which is worse than an error.\n\t */\n\tprivate claim(how: 'awaited' | 'iterated'): void {\n\t\tif (how === 'awaited' && this.consumed === 'awaited') return;\n\t\tif (this.consumed) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\tthis.consumed === how\n\t\t\t\t\t\t? `This turn was already ${how}. Call ask() again to ask again; a turn is a live stream, not a replayable list.`\n\t\t\t\t\t\t: `This turn was already ${this.consumed} and cannot also be ${how}. Pick one: await it for the reply, or iterate it for the events. Call ask() again to ask again.`,\n\t\t\t\tcode: 'turn_already_consumed',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\tthis.consumed = how;\n\t}\n\n\tprivate async body(): Promise<ReadableStream<Uint8Array>> {\n\t\tconst res = await this.response;\n\t\tif (!res.body) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: 'The turn response had no body to stream.',\n\t\t\t\tcode: 'bad_response',\n\t\t\t\tstatus: res.status,\n\t\t\t});\n\t\t}\n\t\treturn res.body;\n\t}\n\n\t/** Drain the stream and resolve to the final reply. */\n\tprivate async settle(): Promise<string> {\n\t\t// `text` fragments accumulate as a fallback. `done` carries the whole reply\n\t\t// and wins when present, but a stream that ends without one should return\n\t\t// what the caller actually saw rather than an empty string, which would read\n\t\t// as \"the agent said nothing\".\n\t\tlet assembled = '';\n\t\tfor await (const event of parseSSE(await this.body())) {\n\t\t\tif (event.type === 'text') assembled += event.text;\n\t\t\tif (event.type === 'done') return event.reply;\n\t\t\tif (event.type === 'error') {\n\t\t\t\t// Awaiting is the shape that throws. Iterating gets the error as an\n\t\t\t\t// event and the loop ends, because a for-await that throws mid-render\n\t\t\t\t// leaves the UI half-painted.\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: event.message,\n\t\t\t\t\tcode: 'turn_failed',\n\t\t\t\t\tstatus: event.retryable ? 503 : 400,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn assembled;\n\t}\n\n\tprivate drain(): Promise<string> {\n\t\t// claim() runs HERE rather than inside settle(), because settle() runs only\n\t\t// once: a second await would skip the check entirely, and awaiting a turn\n\t\t// that was already iterated would quietly return an empty string instead of\n\t\t// saying what went wrong.\n\t\t//\n\t\t// REJECT, never throw. `then` must return a promise; a synchronous throw out\n\t\t// of it escapes the promise chain, so a caller writing\n\t\t// `turn.then(a, b).catch(c)` would get an exception past their own .catch.\n\t\ttry {\n\t\t\tthis.claim('awaited');\n\t\t} catch (err) {\n\t\t\treturn Promise.reject(err);\n\t\t}\n\t\t// Started once. `await` calls `then`, and so does Promise.all, and so does\n\t\t// anything that duck-types a thenable; all of them share one drain.\n\t\tthis.settled ??= this.settle();\n\t\treturn this.settled;\n\t}\n\n\tthen<A = string, B = never>(\n\t\tonOk?: ((value: string) => A | PromiseLike<A>) | null,\n\t\tonErr?: ((reason: unknown) => B | PromiseLike<B>) | null,\n\t): Promise<A | B> {\n\t\treturn this.drain().then(onOk, onErr);\n\t}\n\n\tcatch<B = never>(onErr?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<string | B> {\n\t\treturn this.drain().catch(onErr);\n\t}\n\n\tfinally(onDone?: (() => void) | null): Promise<string> {\n\t\treturn this.drain().finally(onDone);\n\t}\n\n\tasync *[Symbol.asyncIterator](): AsyncGenerator<TurnEvent> {\n\t\tthis.claim('iterated');\n\t\tyield* parseSSE(await this.body());\n\t}\n}\n\nexport function makeTurn(response: Promise<Response>): Turn {\n\treturn new TurnImpl(response) as unknown as Turn;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"turn.js","sourceRoot":"","sources":["../src/turn.ts"],"names":[],"mappings":"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","sourcesContent":["// One turn: `Promise<string> & AsyncIterable<TurnEvent>`.\n//\n// A caller who wants the answer writes `await conversation.ask('...')`. A caller\n// who wants to render it as it arrives writes `for await (const e of\n// conversation.ask('...'))`. One method, two shapes, because making the caller\n// choose `ask` or `askStream` up front means changing the call site later just\n// to show a spinner.\n//\n// THE MESSAGE IS SENT WHEN ask() IS CALLED, not when the result is awaited or\n// iterated. A lazy promise would mean `const t = ask(...)` did nothing and\n// `await t` twice asked twice; sending eagerly makes `ask` the verb it reads as,\n// and calling it twice is then obviously two questions.\n//\n// Awaiting AND iterating the same turn throws rather than replaying. The events\n// are a live stream over a socket, not a stored list, so a second consumer would\n// silently receive nothing at all, which is far worse than an error.\n\nimport { AgentNavaError } from './errors.ts';\nimport type { TurnEvent } from './types.ts';\n\n/** What `conversation.ask()` returns. */\nexport type Turn = Promise<string> & AsyncIterable<TurnEvent>;\n\n/** Parse an SSE body into the events the API sends.\n *\n * Frames are `data: {json}` separated by blank lines. Anything else on the wire\n * (comments, retry directives, a stray blank) is ignored rather than treated as\n * an error: a proxy is allowed to insert keep-alive comments and a stream that\n * died because of one would be maddening to debug. */\nexport async function* parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<TurnEvent> {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tlet buffer = '';\n\tlet completed = false;\n\tlet assembled = '';\n\tlet finalReply: string | undefined;\n\tlet terminal = false;\n\tconst eventForLine = (line: string): TurnEvent | undefined => {\n\t\tconst wire = parseLine(line);\n\t\tif (!wire) return undefined;\n\t\t// Text being final does not prove the turn completed. Persistence or the\n\t\t// observer can still fail before the runtime's terminal done marker.\n\t\tif (wire.type === 'message-end') {\n\t\t\tfinalReply = typeof wire.final === 'string' ? wire.final : '';\n\t\t\treturn undefined;\n\t\t}\n\t\tif (wire.type === 'done') {\n\t\t\tterminal = true;\n\t\t\treturn { type: 'done', reply: finalReply ?? assembled };\n\t\t}\n\t\tconst event = translate(wire);\n\t\tif (event?.type === 'text') assembled += event.text;\n\t\tif (event?.type === 'error') terminal = true;\n\t\treturn event;\n\t};\n\ttry {\n\t\twhile (true) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) {\n\t\t\t\tcompleted = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t// Split on newlines, keeping the last partial line in the buffer. A\n\t\t\t// frame can be split across two network reads at any byte.\n\t\t\tlet nl = buffer.indexOf('\\n');\n\t\t\twhile (nl !== -1) {\n\t\t\t\tconst line = buffer.slice(0, nl).replace(/\\r$/, '');\n\t\t\t\tbuffer = buffer.slice(nl + 1);\n\t\t\t\tconst event = eventForLine(line);\n\t\t\t\tif (event) yield event;\n\t\t\t\tif (terminal) return;\n\t\t\t\tnl = buffer.indexOf('\\n');\n\t\t\t}\n\t\t}\n\t\t// A final frame with no trailing newline still counts.\n\t\tconst last = eventForLine(buffer.replace(/\\r$/, ''));\n\t\tif (last) yield last;\n\t\tif (!terminal) yield { type: 'error', message: 'The response stream ended before the turn finished.', retryable: false };\n\t} catch (error) {\n\t\t// Explicit cancellation keeps its AbortError identity. A lost transport\n\t\t// is an interrupted observer, never evidence that asking again is safe.\n\t\tif (error instanceof Error && error.name === 'AbortError') throw error;\n\t\tyield { type: 'error', message: `The response stream was interrupted: ${error instanceof Error ? error.message : String(error)}`, retryable: false };\n\t} finally {\n\t\t// A final reply, a wire error, or a caller's early break can stop before\n\t\t// EOF. Releasing the lock alone leaves the HTTP body and abort listener\n\t\t// alive; cancel unread data before releasing ownership of the reader.\n\t\ttry {\n\t\t\t// Cleanup can be slow or never settle. Initiate it before releasing\n\t\t\t// ownership, but do not delay the reply/error or iterator return.\n\t\t\tif (!completed) void reader.cancel().catch(() => {});\n\t\t} catch {\n\t\t\t// Cleanup must not replace the reply or the original stream/wire error.\n\t\t} finally {\n\t\t\treader.releaseLock();\n\t\t}\n\t}\n}\n\n/** One frame as it arrives on the wire, before translation. */\ninterface WireEvent {\n\ttype: string;\n\tdelta?: string;\n\tfinal?: string;\n\tname?: string;\n\tok?: boolean;\n\tmessage?: string;\n\tretryable?: boolean;\n\tpath?: string;\n}\n\n/**\n * The API's event vocabulary, as the SDK's.\n *\n * The wire says `message-delta` / `message-end` / `tool-start` / `tool-end`, and\n * design/sdk-design.md says `text` / `done` / `tool`. Both are right for their\n * own audience: the wire shape is what apps/web's chat renders and has rendered\n * for a long time, and the SDK shape is what reads well at a call site\n * (`if (e.type === 'text')`).\n *\n * So this translates, which is the same job the SDK already does for drive\n * entries and connection rows. It is NOT a rename waiting to happen: changing\n * the wire would break a working chat UI to save a switch statement.\n *\n * Returns undefined for a frame with nothing to report, including an event type\n * this version does not know. A new event must never break an old client.\n */\nfunction translate(wire: WireEvent): TurnEvent | undefined {\n\tswitch (wire.type) {\n\t\tcase 'message-delta':\n\t\t\t// A FRAGMENT. Append, do not assemble.\n\t\t\treturn typeof wire.delta === 'string' && wire.delta.length > 0\n\t\t\t\t? { type: 'text', text: wire.delta }\n\t\t\t\t: undefined;\n\t\tcase 'tool-start':\n\t\t\treturn { type: 'tool', name: wire.name ?? 'tool', state: 'start' };\n\t\tcase 'tool-end':\n\t\t\treturn {\n\t\t\t\ttype: 'tool',\n\t\t\t\tname: wire.name ?? 'tool',\n\t\t\t\tstate: 'end',\n\t\t\t\t...(wire.ok !== undefined && { ok: wire.ok }),\n\t\t\t};\n\t\tcase 'error':\n\t\t\treturn {\n\t\t\t\ttype: 'error',\n\t\t\t\tmessage: wire.message ?? 'the turn failed',\n\t\t\t\tretryable: wire.retryable === true,\n\t\t\t};\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\nfunction parseLine(line: string): WireEvent | undefined {\n\tif (!line.startsWith('data:')) return undefined;\n\tconst payload = line.slice(5).trim();\n\tif (!payload || payload === '[DONE]') return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(payload) as WireEvent | null;\n\t\treturn parsed && typeof parsed.type === 'string' ? parsed : undefined;\n\t} catch {\n\t\t// A frame we cannot parse is dropped rather than thrown. A malformed frame\n\t\t// must not break a stream that is otherwise fine.\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Build the dual-shaped result.\n *\n * `response` is already in flight when this is called: the caller passes the\n * running request, so the turn is sent whether or not anyone ever consumes it.\n *\n * A THENABLE, not a Promise subclass, and that was measured rather than assumed.\n * A subclass whose base promise never settles deadlocks, because `await` can\n * settle against the base's own state instead of the overridden `then`. A\n * subclass whose base resolves immediately fixes the deadlock and passes\n * `instanceof Promise`, but then anything reading the promise's INTERNAL state\n * sees \"resolved\" for a turn whose `then` rejects, which is a lie that tooling\n * believes. A thenable is honest: `await`, `Promise.all` and `.then/.catch/\n * .finally` are all defined in terms of `then` and all work, and anything that\n * demands a real Promise says so out loud instead of reporting a wrong answer.\n */\nclass TurnImpl implements AsyncIterable<TurnEvent> {\n\tprivate consumed?: 'awaited' | 'iterated';\n\tprivate settled?: Promise<string>;\n\n\tconstructor(private readonly response: Promise<Response>, private readonly recoveryMessage = '') {\n\t\t// ask() starts eagerly, while consuming its thenable may happen later.\n\t\t// Observe early rejection now; keep the ORIGINAL promise so a later\n\t\t// await/iteration still receives that same error.\n\t\tvoid response.catch(() => {});\n\t}\n\n\t/**\n\t * Record how this turn is being consumed, and refuse a second, different way.\n\t *\n\t * AWAITING TWICE IS ALLOWED, deliberately. A promise handing the same value to\n\t * two awaits is exactly what a promise is, and `drain()` memoises so the second\n\t * await costs nothing. Refusing it would break `Promise.all([turn, turn])` and\n\t * every library that attaches a `.catch()` and then awaits the same value.\n\t *\n\t * ITERATING TWICE IS NOT, and neither is mixing the two. The events are a live\n\t * stream over a socket, not a stored list: a second iterator would receive\n\t * nothing at all and report an empty turn, which is worse than an error.\n\t */\n\tprivate claim(how: 'awaited' | 'iterated'): void {\n\t\tif (how === 'awaited' && this.consumed === 'awaited') return;\n\t\tif (this.consumed) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\tthis.consumed === how\n\t\t\t\t\t\t? `This turn was already ${how}. Call ask() again to ask again; a turn is a live stream, not a replayable list.`\n\t\t\t\t\t\t: `This turn was already ${this.consumed} and cannot also be ${how}. Pick one: await it for the reply, or iterate it for the events. Call ask() again to ask again.`,\n\t\t\t\tcode: 'turn_already_consumed',\n\t\t\t\tstatus: 400,\n\t\t\t});\n\t\t}\n\t\tthis.consumed = how;\n\t}\n\n\tprivate async body(): Promise<ReadableStream<Uint8Array>> {\n\t\tconst res = await this.response;\n\t\tif (!res.body) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `The turn response had no body to stream. ${this.recoveryMessage}`.trim(),\n\t\t\t\tcode: 'bad_response',\n\t\t\t\tstatus: res.status,\n\t\t\t});\n\t\t}\n\t\treturn res.body;\n\t}\n\n\t/** Drain the stream and resolve to the final reply. */\n\tprivate async settle(): Promise<string> {\n\t\t// parseSSE withholds the final answer until wire done confirms completion.\n\t\t// EOF is an interruption even if some or all of the reply already arrived.\n\t\tfor await (const event of parseSSE(await this.body())) {\n\t\t\tif (event.type === 'done') return event.reply;\n\t\t\tif (event.type === 'error') {\n\t\t\t\t// Awaiting is the shape that throws. Iterating gets the error as an\n\t\t\t\t// event and the loop ends, because a for-await that throws mid-render\n\t\t\t\t// leaves the UI half-painted.\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `${event.message} ${this.recoveryMessage}`.trim(),\n\t\t\t\t\tcode: 'turn_failed',\n\t\t\t\t\tstatus: event.retryable ? 503 : 400,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tthrow new AgentNavaError({ message: `The response stream ended before the turn finished. ${this.recoveryMessage}`.trim(), code: 'turn_failed', status: 400 });\n\t}\n\n\tprivate drain(): Promise<string> {\n\t\t// claim() runs HERE rather than inside settle(), because settle() runs only\n\t\t// once: a second await would skip the check entirely, and awaiting a turn\n\t\t// that was already iterated would quietly return an empty string instead of\n\t\t// saying what went wrong.\n\t\t//\n\t\t// REJECT, never throw. `then` must return a promise; a synchronous throw out\n\t\t// of it escapes the promise chain, so a caller writing\n\t\t// `turn.then(a, b).catch(c)` would get an exception past their own .catch.\n\t\ttry {\n\t\t\tthis.claim('awaited');\n\t\t} catch (err) {\n\t\t\treturn Promise.reject(err);\n\t\t}\n\t\t// Started once. `await` calls `then`, and so does Promise.all, and so does\n\t\t// anything that duck-types a thenable; all of them share one drain.\n\t\tthis.settled ??= this.settle();\n\t\treturn this.settled;\n\t}\n\n\tthen<A = string, B = never>(\n\t\tonOk?: ((value: string) => A | PromiseLike<A>) | null,\n\t\tonErr?: ((reason: unknown) => B | PromiseLike<B>) | null,\n\t): Promise<A | B> {\n\t\treturn this.drain().then(onOk, onErr);\n\t}\n\n\tcatch<B = never>(onErr?: ((reason: unknown) => B | PromiseLike<B>) | null): Promise<string | B> {\n\t\treturn this.drain().catch(onErr);\n\t}\n\n\tfinally(onDone?: (() => void) | null): Promise<string> {\n\t\treturn this.drain().finally(onDone);\n\t}\n\n\tasync *[Symbol.asyncIterator](): AsyncGenerator<TurnEvent> {\n\t\tthis.claim('iterated');\n\t\tfor await (const event of parseSSE(await this.body())) {\n\t\t\tyield event.type === 'error' && this.recoveryMessage\n\t\t\t\t? { ...event, message: `${event.message} ${this.recoveryMessage}` }\n\t\t\t\t: event;\n\t\t}\n\t}\n}\n\nexport function makeTurn(response: Promise<Response>, recoveryMessage = ''): Turn {\n\treturn new TurnImpl(response, recoveryMessage) as unknown as Turn;\n}\n"]}
|