@copass/ai-sdk
Advanced tools
+6
-1
@@ -58,3 +58,8 @@ "use strict"; | ||
| subgraph: item.subgraph ?? null, | ||
| matched_query_nodes: item.matched_query_nodes ?? null | ||
| matched_query_nodes: item.matched_query_nodes ?? null, | ||
| // Inline source-file paths so the agent can `read` directly | ||
| // without a follow-up `get_origin` call. Empty array when | ||
| // the backend couldn't enrich (legacy sandbox / no file | ||
| // markers in source chunks). | ||
| file_paths: item.file_paths ?? [] | ||
| })), | ||
@@ -61,0 +66,0 @@ next_steps: response.next_steps |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/index.ts","../src/tools.ts","../src/window-tracker.ts"],"sourcesContent":["export { copassTools } from './tools.js';\nexport type { CopassToolsOptions } from './tools.js';\nexport { createWindowTracker } from './window-tracker.js';\nexport type {\n WindowTracker,\n WindowTrackerOptions,\n StepFinishLike,\n} from './window-tracker.js';\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport {\n DISCOVER_DESCRIPTION,\n DISCOVER_QUERY_PARAM,\n GET_ORIGIN_DESCRIPTION,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n ORIGIN_CANONICAL_IDS_PARAM,\n ORIGIN_LIMIT_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type {\n CopassClient,\n OriginEntry,\n OriginFile,\n SearchPreset,\n WindowLike,\n} from '@copass/core';\n\nexport interface CopassToolsOptions {\n /** An authenticated `@copass/core` client. */\n client: CopassClient;\n /** Sandbox all retrieval runs against. */\n sandbox_id: string;\n /** Optional project scoping for retrieval calls. */\n project_id?: string;\n /**\n * Optional conversation window — a {@link WindowLike} (typically a\n * `ContextWindow` from `client.contextWindow.create()`). When provided,\n * every retrieval call is automatically window-aware.\n */\n window?: WindowLike;\n /**\n * Preset for `discover`, `interpret`, and `search`. Defaults to\n * `\"copass/copass_1.0\"`. Under `\"copass/copass_2.0\"` discover items\n * carry `subgraph` (pre-rendered ASCII tree) and `matched_query_nodes`\n * fields. Append `\":thinking\"` (e.g. `\"copass/copass_2.0:thinking\"`)\n * to enable task decomposition before retrieval on `search`.\n */\n preset?: SearchPreset;\n}\n\n/**\n * Return Copass retrieval as a set of Vercel AI SDK tool objects.\n *\n * Agent-framework-neutral shape: the LLM picks `discover` (menu of\n * relevant items) or `search` (synthesized answer). `interpret` stays\n * registered for back-compat but is legacy — prefer `search` for drill-in.\n *\n * @example\n * ```ts\n * import { generateText } from 'ai';\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { copassTools } from '@copass/ai-sdk';\n *\n * const window = await copass.contextWindow.create({ sandbox_id });\n *\n * const { text } = await generateText({\n * model: anthropic('claude-opus-4-7'),\n * tools: copassTools({ client: copass, sandbox_id, window }),\n * maxSteps: 5,\n * prompt: 'why is checkout flaky?',\n * });\n * ```\n */\nexport function copassTools(options: CopassToolsOptions) {\n const { client, sandbox_id, project_id, window, preset = 'copass/copass_1.0' } = options;\n\n return {\n discover: tool({\n description: DISCOVER_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.discover(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return {\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1 — agents can ignore them when not present.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n };\n },\n }),\n\n interpret: tool({\n description: INTERPRET_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string())).min(1).describe(INTERPRET_ITEMS_PARAM),\n }),\n execute: async ({ query, items }) => {\n const response = await client.retrieval.interpret(sandbox_id, {\n query,\n items,\n project_id,\n window,\n preset,\n });\n return { brief: response.brief };\n },\n }),\n\n search: tool({\n description: SEARCH_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(SEARCH_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.search(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return { answer: response.answer };\n },\n }),\n\n get_origin: tool({\n description: GET_ORIGIN_DESCRIPTION,\n inputSchema: z.object({\n canonical_ids: z\n .array(z.string())\n .min(1)\n .max(100)\n .describe(ORIGIN_CANONICAL_IDS_PARAM),\n limit_per_canonical: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(ORIGIN_LIMIT_PARAM),\n }),\n execute: async ({ canonical_ids, limit_per_canonical }) => {\n const response = await client.retrieval.getOrigin(sandbox_id, {\n canonical_ids,\n ...(limit_per_canonical !== undefined ? { limit_per_canonical } : {}),\n });\n return {\n sandbox_id: response.sandbox_id,\n origins: response.origins.map((entry: OriginEntry) => ({\n canonical_id: entry.canonical_id,\n files: entry.files.map((f: OriginFile) => ({\n file_path: f.file_path,\n extraction_count: f.extraction_count,\n })),\n })),\n };\n },\n }),\n };\n}\n","import type { ChatMessage, ChatRole, ContextWindow } from '@copass/core';\n\n/**\n * Minimal shape of a Vercel AI SDK step finish event — typed structurally so\n * this module doesn't lock you to an `ai` major version.\n */\nexport interface StepFinishLike {\n response?: {\n messages?: Array<{\n role?: string;\n content?: unknown;\n }>;\n };\n}\n\nexport interface WindowTrackerOptions {\n /** The Context Window to mirror messages into. */\n window: ContextWindow;\n /**\n * Include `tool` messages (results returned by a tool call) as turns.\n * Default: false — tool results are usually retrieval noise.\n */\n includeToolMessages?: boolean;\n}\n\nexport interface WindowTracker {\n /**\n * Pass to Vercel AI SDK's `generateText` / `streamText` as `onStepFinish`.\n * Each step's assistant + tool output messages land in the window,\n * de-duplicated against what's already there.\n */\n onStepFinish: (step: StepFinishLike) => Promise<void>;\n /**\n * Record the user's turn before you call `generateText`. Vercel's\n * `onStepFinish` only surfaces messages *generated* during a step, so the\n * user's initial input has to be captured explicitly. Safe to call even if\n * the message was already added — the tracker de-duplicates.\n */\n recordUserTurn: (content: string) => Promise<void>;\n}\n\n/**\n * Build a window tracker for Vercel AI SDK agent loops.\n *\n * @example\n * ```ts\n * import { copassTools, createWindowTracker } from '@copass/ai-sdk';\n *\n * const tools = copassTools({ client, sandbox_id, window });\n * const tracker = createWindowTracker({ window });\n *\n * await tracker.recordUserTurn(userMessage);\n * const { text } = await generateText({\n * model,\n * tools,\n * onStepFinish: tracker.onStepFinish,\n * prompt: userMessage,\n * maxSteps: 5,\n * });\n * ```\n */\nexport function createWindowTracker(options: WindowTrackerOptions): WindowTracker {\n const { window, includeToolMessages = false } = options;\n const seen = new Set<string>();\n for (const turn of window.getTurns()) {\n seen.add(hashTurn(turn));\n }\n\n async function addIfNew(turn: ChatMessage): Promise<void> {\n if (!turn.content.trim()) return;\n const key = hashTurn(turn);\n if (seen.has(key)) return;\n seen.add(key);\n try {\n await window.addTurn(turn);\n } catch {\n // Best-effort — missing a turn still lets retrieval see prior ones.\n }\n }\n\n return {\n async onStepFinish(step) {\n const messages = step.response?.messages ?? [];\n for (const msg of messages) {\n const role = roleFromRole(msg.role, includeToolMessages);\n if (!role) continue;\n await addIfNew({ role, content: contentToString(msg.content) });\n }\n },\n recordUserTurn(content) {\n return addIfNew({ role: 'user', content });\n },\n };\n}\n\nfunction roleFromRole(role: string | undefined, includeToolMessages: boolean): ChatRole | null {\n switch (role) {\n case 'user':\n return 'user';\n case 'assistant':\n return 'assistant';\n case 'system':\n return 'system';\n case 'tool':\n return includeToolMessages ? 'system' : null;\n default:\n return null;\n }\n}\n\nfunction contentToString(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return String(content ?? '');\n return content\n .map((part) => {\n if (typeof part === 'string') return part;\n if (part && typeof part === 'object') {\n const rec = part as Record<string, unknown>;\n if (typeof rec.text === 'string') return rec.text;\n }\n return '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction hashTurn(turn: ChatMessage): string {\n return `${turn.role}:${turn.content.slice(0, 500)}`;\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/index.ts","../src/tools.ts","../src/window-tracker.ts"],"sourcesContent":["export { copassTools } from './tools.js';\nexport type { CopassToolsOptions } from './tools.js';\nexport { createWindowTracker } from './window-tracker.js';\nexport type {\n WindowTracker,\n WindowTrackerOptions,\n StepFinishLike,\n} from './window-tracker.js';\n","import { tool } from 'ai';\nimport { z } from 'zod';\nimport {\n DISCOVER_DESCRIPTION,\n DISCOVER_QUERY_PARAM,\n GET_ORIGIN_DESCRIPTION,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n ORIGIN_CANONICAL_IDS_PARAM,\n ORIGIN_LIMIT_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type {\n CopassClient,\n OriginEntry,\n OriginFile,\n SearchPreset,\n WindowLike,\n} from '@copass/core';\n\nexport interface CopassToolsOptions {\n /** An authenticated `@copass/core` client. */\n client: CopassClient;\n /** Sandbox all retrieval runs against. */\n sandbox_id: string;\n /** Optional project scoping for retrieval calls. */\n project_id?: string;\n /**\n * Optional conversation window — a {@link WindowLike} (typically a\n * `ContextWindow` from `client.contextWindow.create()`). When provided,\n * every retrieval call is automatically window-aware.\n */\n window?: WindowLike;\n /**\n * Preset for `discover`, `interpret`, and `search`. Defaults to\n * `\"copass/copass_1.0\"`. Under `\"copass/copass_2.0\"` discover items\n * carry `subgraph` (pre-rendered ASCII tree) and `matched_query_nodes`\n * fields. Append `\":thinking\"` (e.g. `\"copass/copass_2.0:thinking\"`)\n * to enable task decomposition before retrieval on `search`.\n */\n preset?: SearchPreset;\n}\n\n/**\n * Return Copass retrieval as a set of Vercel AI SDK tool objects.\n *\n * Agent-framework-neutral shape: the LLM picks `discover` (menu of\n * relevant items) or `search` (synthesized answer). `interpret` stays\n * registered for back-compat but is legacy — prefer `search` for drill-in.\n *\n * @example\n * ```ts\n * import { generateText } from 'ai';\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { copassTools } from '@copass/ai-sdk';\n *\n * const window = await copass.contextWindow.create({ sandbox_id });\n *\n * const { text } = await generateText({\n * model: anthropic('claude-opus-4-7'),\n * tools: copassTools({ client: copass, sandbox_id, window }),\n * maxSteps: 5,\n * prompt: 'why is checkout flaky?',\n * });\n * ```\n */\nexport function copassTools(options: CopassToolsOptions) {\n const { client, sandbox_id, project_id, window, preset = 'copass/copass_1.0' } = options;\n\n return {\n discover: tool({\n description: DISCOVER_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.discover(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return {\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1 — agents can ignore them when not present.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n // Inline source-file paths so the agent can `read` directly\n // without a follow-up `get_origin` call. Empty array when\n // the backend couldn't enrich (legacy sandbox / no file\n // markers in source chunks).\n file_paths: item.file_paths ?? [],\n })),\n next_steps: response.next_steps,\n };\n },\n }),\n\n interpret: tool({\n description: INTERPRET_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string())).min(1).describe(INTERPRET_ITEMS_PARAM),\n }),\n execute: async ({ query, items }) => {\n const response = await client.retrieval.interpret(sandbox_id, {\n query,\n items,\n project_id,\n window,\n preset,\n });\n return { brief: response.brief };\n },\n }),\n\n search: tool({\n description: SEARCH_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(SEARCH_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.search(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return { answer: response.answer };\n },\n }),\n\n get_origin: tool({\n description: GET_ORIGIN_DESCRIPTION,\n inputSchema: z.object({\n canonical_ids: z\n .array(z.string())\n .min(1)\n .max(100)\n .describe(ORIGIN_CANONICAL_IDS_PARAM),\n limit_per_canonical: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(ORIGIN_LIMIT_PARAM),\n }),\n execute: async ({ canonical_ids, limit_per_canonical }) => {\n const response = await client.retrieval.getOrigin(sandbox_id, {\n canonical_ids,\n ...(limit_per_canonical !== undefined ? { limit_per_canonical } : {}),\n });\n return {\n sandbox_id: response.sandbox_id,\n origins: response.origins.map((entry: OriginEntry) => ({\n canonical_id: entry.canonical_id,\n files: entry.files.map((f: OriginFile) => ({\n file_path: f.file_path,\n extraction_count: f.extraction_count,\n })),\n })),\n };\n },\n }),\n };\n}\n","import type { ChatMessage, ChatRole, ContextWindow } from '@copass/core';\n\n/**\n * Minimal shape of a Vercel AI SDK step finish event — typed structurally so\n * this module doesn't lock you to an `ai` major version.\n */\nexport interface StepFinishLike {\n response?: {\n messages?: Array<{\n role?: string;\n content?: unknown;\n }>;\n };\n}\n\nexport interface WindowTrackerOptions {\n /** The Context Window to mirror messages into. */\n window: ContextWindow;\n /**\n * Include `tool` messages (results returned by a tool call) as turns.\n * Default: false — tool results are usually retrieval noise.\n */\n includeToolMessages?: boolean;\n}\n\nexport interface WindowTracker {\n /**\n * Pass to Vercel AI SDK's `generateText` / `streamText` as `onStepFinish`.\n * Each step's assistant + tool output messages land in the window,\n * de-duplicated against what's already there.\n */\n onStepFinish: (step: StepFinishLike) => Promise<void>;\n /**\n * Record the user's turn before you call `generateText`. Vercel's\n * `onStepFinish` only surfaces messages *generated* during a step, so the\n * user's initial input has to be captured explicitly. Safe to call even if\n * the message was already added — the tracker de-duplicates.\n */\n recordUserTurn: (content: string) => Promise<void>;\n}\n\n/**\n * Build a window tracker for Vercel AI SDK agent loops.\n *\n * @example\n * ```ts\n * import { copassTools, createWindowTracker } from '@copass/ai-sdk';\n *\n * const tools = copassTools({ client, sandbox_id, window });\n * const tracker = createWindowTracker({ window });\n *\n * await tracker.recordUserTurn(userMessage);\n * const { text } = await generateText({\n * model,\n * tools,\n * onStepFinish: tracker.onStepFinish,\n * prompt: userMessage,\n * maxSteps: 5,\n * });\n * ```\n */\nexport function createWindowTracker(options: WindowTrackerOptions): WindowTracker {\n const { window, includeToolMessages = false } = options;\n const seen = new Set<string>();\n for (const turn of window.getTurns()) {\n seen.add(hashTurn(turn));\n }\n\n async function addIfNew(turn: ChatMessage): Promise<void> {\n if (!turn.content.trim()) return;\n const key = hashTurn(turn);\n if (seen.has(key)) return;\n seen.add(key);\n try {\n await window.addTurn(turn);\n } catch {\n // Best-effort — missing a turn still lets retrieval see prior ones.\n }\n }\n\n return {\n async onStepFinish(step) {\n const messages = step.response?.messages ?? [];\n for (const msg of messages) {\n const role = roleFromRole(msg.role, includeToolMessages);\n if (!role) continue;\n await addIfNew({ role, content: contentToString(msg.content) });\n }\n },\n recordUserTurn(content) {\n return addIfNew({ role: 'user', content });\n },\n };\n}\n\nfunction roleFromRole(role: string | undefined, includeToolMessages: boolean): ChatRole | null {\n switch (role) {\n case 'user':\n return 'user';\n case 'assistant':\n return 'assistant';\n case 'system':\n return 'system';\n case 'tool':\n return includeToolMessages ? 'system' : null;\n default:\n return null;\n }\n}\n\nfunction contentToString(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return String(content ?? '');\n return content\n .map((part) => {\n if (typeof part === 'string') return part;\n if (part && typeof part === 'object') {\n const rec = part as Record<string, unknown>;\n if (typeof rec.text === 'string') return rec.text;\n }\n return '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction hashTurn(turn: ChatMessage): string {\n return `${turn.role}:${turn.content.slice(0, 500)}`;\n}\n"],"mappings":"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","names":[]} |
+1
-0
@@ -60,2 +60,3 @@ import * as ai from 'ai'; | ||
| matched_query_nodes: string[] | null; | ||
| file_paths: string[]; | ||
| }[]; | ||
@@ -62,0 +63,0 @@ next_steps: string; |
+1
-0
@@ -60,2 +60,3 @@ import * as ai from 'ai'; | ||
| matched_query_nodes: string[] | null; | ||
| file_paths: string[]; | ||
| }[]; | ||
@@ -62,0 +63,0 @@ next_steps: string; |
+6
-1
@@ -42,3 +42,8 @@ // src/tools.ts | ||
| subgraph: item.subgraph ?? null, | ||
| matched_query_nodes: item.matched_query_nodes ?? null | ||
| matched_query_nodes: item.matched_query_nodes ?? null, | ||
| // Inline source-file paths so the agent can `read` directly | ||
| // without a follow-up `get_origin` call. Empty array when | ||
| // the backend couldn't enrich (legacy sandbox / no file | ||
| // markers in source chunks). | ||
| file_paths: item.file_paths ?? [] | ||
| })), | ||
@@ -45,0 +50,0 @@ next_steps: response.next_steps |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/tools.ts","../src/window-tracker.ts"],"sourcesContent":["import { tool } from 'ai';\nimport { z } from 'zod';\nimport {\n DISCOVER_DESCRIPTION,\n DISCOVER_QUERY_PARAM,\n GET_ORIGIN_DESCRIPTION,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n ORIGIN_CANONICAL_IDS_PARAM,\n ORIGIN_LIMIT_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type {\n CopassClient,\n OriginEntry,\n OriginFile,\n SearchPreset,\n WindowLike,\n} from '@copass/core';\n\nexport interface CopassToolsOptions {\n /** An authenticated `@copass/core` client. */\n client: CopassClient;\n /** Sandbox all retrieval runs against. */\n sandbox_id: string;\n /** Optional project scoping for retrieval calls. */\n project_id?: string;\n /**\n * Optional conversation window — a {@link WindowLike} (typically a\n * `ContextWindow` from `client.contextWindow.create()`). When provided,\n * every retrieval call is automatically window-aware.\n */\n window?: WindowLike;\n /**\n * Preset for `discover`, `interpret`, and `search`. Defaults to\n * `\"copass/copass_1.0\"`. Under `\"copass/copass_2.0\"` discover items\n * carry `subgraph` (pre-rendered ASCII tree) and `matched_query_nodes`\n * fields. Append `\":thinking\"` (e.g. `\"copass/copass_2.0:thinking\"`)\n * to enable task decomposition before retrieval on `search`.\n */\n preset?: SearchPreset;\n}\n\n/**\n * Return Copass retrieval as a set of Vercel AI SDK tool objects.\n *\n * Agent-framework-neutral shape: the LLM picks `discover` (menu of\n * relevant items) or `search` (synthesized answer). `interpret` stays\n * registered for back-compat but is legacy — prefer `search` for drill-in.\n *\n * @example\n * ```ts\n * import { generateText } from 'ai';\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { copassTools } from '@copass/ai-sdk';\n *\n * const window = await copass.contextWindow.create({ sandbox_id });\n *\n * const { text } = await generateText({\n * model: anthropic('claude-opus-4-7'),\n * tools: copassTools({ client: copass, sandbox_id, window }),\n * maxSteps: 5,\n * prompt: 'why is checkout flaky?',\n * });\n * ```\n */\nexport function copassTools(options: CopassToolsOptions) {\n const { client, sandbox_id, project_id, window, preset = 'copass/copass_1.0' } = options;\n\n return {\n discover: tool({\n description: DISCOVER_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.discover(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return {\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1 — agents can ignore them when not present.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n })),\n next_steps: response.next_steps,\n };\n },\n }),\n\n interpret: tool({\n description: INTERPRET_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string())).min(1).describe(INTERPRET_ITEMS_PARAM),\n }),\n execute: async ({ query, items }) => {\n const response = await client.retrieval.interpret(sandbox_id, {\n query,\n items,\n project_id,\n window,\n preset,\n });\n return { brief: response.brief };\n },\n }),\n\n search: tool({\n description: SEARCH_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(SEARCH_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.search(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return { answer: response.answer };\n },\n }),\n\n get_origin: tool({\n description: GET_ORIGIN_DESCRIPTION,\n inputSchema: z.object({\n canonical_ids: z\n .array(z.string())\n .min(1)\n .max(100)\n .describe(ORIGIN_CANONICAL_IDS_PARAM),\n limit_per_canonical: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(ORIGIN_LIMIT_PARAM),\n }),\n execute: async ({ canonical_ids, limit_per_canonical }) => {\n const response = await client.retrieval.getOrigin(sandbox_id, {\n canonical_ids,\n ...(limit_per_canonical !== undefined ? { limit_per_canonical } : {}),\n });\n return {\n sandbox_id: response.sandbox_id,\n origins: response.origins.map((entry: OriginEntry) => ({\n canonical_id: entry.canonical_id,\n files: entry.files.map((f: OriginFile) => ({\n file_path: f.file_path,\n extraction_count: f.extraction_count,\n })),\n })),\n };\n },\n }),\n };\n}\n","import type { ChatMessage, ChatRole, ContextWindow } from '@copass/core';\n\n/**\n * Minimal shape of a Vercel AI SDK step finish event — typed structurally so\n * this module doesn't lock you to an `ai` major version.\n */\nexport interface StepFinishLike {\n response?: {\n messages?: Array<{\n role?: string;\n content?: unknown;\n }>;\n };\n}\n\nexport interface WindowTrackerOptions {\n /** The Context Window to mirror messages into. */\n window: ContextWindow;\n /**\n * Include `tool` messages (results returned by a tool call) as turns.\n * Default: false — tool results are usually retrieval noise.\n */\n includeToolMessages?: boolean;\n}\n\nexport interface WindowTracker {\n /**\n * Pass to Vercel AI SDK's `generateText` / `streamText` as `onStepFinish`.\n * Each step's assistant + tool output messages land in the window,\n * de-duplicated against what's already there.\n */\n onStepFinish: (step: StepFinishLike) => Promise<void>;\n /**\n * Record the user's turn before you call `generateText`. Vercel's\n * `onStepFinish` only surfaces messages *generated* during a step, so the\n * user's initial input has to be captured explicitly. Safe to call even if\n * the message was already added — the tracker de-duplicates.\n */\n recordUserTurn: (content: string) => Promise<void>;\n}\n\n/**\n * Build a window tracker for Vercel AI SDK agent loops.\n *\n * @example\n * ```ts\n * import { copassTools, createWindowTracker } from '@copass/ai-sdk';\n *\n * const tools = copassTools({ client, sandbox_id, window });\n * const tracker = createWindowTracker({ window });\n *\n * await tracker.recordUserTurn(userMessage);\n * const { text } = await generateText({\n * model,\n * tools,\n * onStepFinish: tracker.onStepFinish,\n * prompt: userMessage,\n * maxSteps: 5,\n * });\n * ```\n */\nexport function createWindowTracker(options: WindowTrackerOptions): WindowTracker {\n const { window, includeToolMessages = false } = options;\n const seen = new Set<string>();\n for (const turn of window.getTurns()) {\n seen.add(hashTurn(turn));\n }\n\n async function addIfNew(turn: ChatMessage): Promise<void> {\n if (!turn.content.trim()) return;\n const key = hashTurn(turn);\n if (seen.has(key)) return;\n seen.add(key);\n try {\n await window.addTurn(turn);\n } catch {\n // Best-effort — missing a turn still lets retrieval see prior ones.\n }\n }\n\n return {\n async onStepFinish(step) {\n const messages = step.response?.messages ?? [];\n for (const msg of messages) {\n const role = roleFromRole(msg.role, includeToolMessages);\n if (!role) continue;\n await addIfNew({ role, content: contentToString(msg.content) });\n }\n },\n recordUserTurn(content) {\n return addIfNew({ role: 'user', content });\n },\n };\n}\n\nfunction roleFromRole(role: string | undefined, includeToolMessages: boolean): ChatRole | null {\n switch (role) {\n case 'user':\n return 'user';\n case 'assistant':\n return 'assistant';\n case 'system':\n return 'system';\n case 'tool':\n return includeToolMessages ? 'system' : null;\n default:\n return null;\n }\n}\n\nfunction contentToString(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return String(content ?? '');\n return content\n .map((part) => {\n if (typeof part === 'string') return part;\n if (part && typeof part === 'object') {\n const rec = part as Record<string, unknown>;\n if (typeof rec.text === 'string') return rec.text;\n }\n return '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction hashTurn(turn: ChatMessage): string {\n return `${turn.role}:${turn.content.slice(0, 500)}`;\n}\n"],"mappings":"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","names":[]} | ||
| {"version":3,"sources":["../src/tools.ts","../src/window-tracker.ts"],"sourcesContent":["import { tool } from 'ai';\nimport { z } from 'zod';\nimport {\n DISCOVER_DESCRIPTION,\n DISCOVER_QUERY_PARAM,\n GET_ORIGIN_DESCRIPTION,\n INTERPRET_DESCRIPTION,\n INTERPRET_ITEMS_PARAM,\n INTERPRET_QUERY_PARAM,\n ORIGIN_CANONICAL_IDS_PARAM,\n ORIGIN_LIMIT_PARAM,\n SEARCH_DESCRIPTION,\n SEARCH_QUERY_PARAM,\n} from '@copass/config';\nimport type {\n CopassClient,\n OriginEntry,\n OriginFile,\n SearchPreset,\n WindowLike,\n} from '@copass/core';\n\nexport interface CopassToolsOptions {\n /** An authenticated `@copass/core` client. */\n client: CopassClient;\n /** Sandbox all retrieval runs against. */\n sandbox_id: string;\n /** Optional project scoping for retrieval calls. */\n project_id?: string;\n /**\n * Optional conversation window — a {@link WindowLike} (typically a\n * `ContextWindow` from `client.contextWindow.create()`). When provided,\n * every retrieval call is automatically window-aware.\n */\n window?: WindowLike;\n /**\n * Preset for `discover`, `interpret`, and `search`. Defaults to\n * `\"copass/copass_1.0\"`. Under `\"copass/copass_2.0\"` discover items\n * carry `subgraph` (pre-rendered ASCII tree) and `matched_query_nodes`\n * fields. Append `\":thinking\"` (e.g. `\"copass/copass_2.0:thinking\"`)\n * to enable task decomposition before retrieval on `search`.\n */\n preset?: SearchPreset;\n}\n\n/**\n * Return Copass retrieval as a set of Vercel AI SDK tool objects.\n *\n * Agent-framework-neutral shape: the LLM picks `discover` (menu of\n * relevant items) or `search` (synthesized answer). `interpret` stays\n * registered for back-compat but is legacy — prefer `search` for drill-in.\n *\n * @example\n * ```ts\n * import { generateText } from 'ai';\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { copassTools } from '@copass/ai-sdk';\n *\n * const window = await copass.contextWindow.create({ sandbox_id });\n *\n * const { text } = await generateText({\n * model: anthropic('claude-opus-4-7'),\n * tools: copassTools({ client: copass, sandbox_id, window }),\n * maxSteps: 5,\n * prompt: 'why is checkout flaky?',\n * });\n * ```\n */\nexport function copassTools(options: CopassToolsOptions) {\n const { client, sandbox_id, project_id, window, preset = 'copass/copass_1.0' } = options;\n\n return {\n discover: tool({\n description: DISCOVER_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(DISCOVER_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.discover(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return {\n header: response.header,\n // Project the v2 fields (`subgraph` + `matched_query_nodes`)\n // alongside the v1 fields. Populated only under\n // `copass/copass_2.0` (or its `copass/2.0` alias); `null`\n // under v1 — agents can ignore them when not present.\n items: response.items.map((item) => ({\n score: item.score,\n summary: item.summary,\n canonical_ids: item.canonical_ids,\n subgraph: item.subgraph ?? null,\n matched_query_nodes: item.matched_query_nodes ?? null,\n // Inline source-file paths so the agent can `read` directly\n // without a follow-up `get_origin` call. Empty array when\n // the backend couldn't enrich (legacy sandbox / no file\n // markers in source chunks).\n file_paths: item.file_paths ?? [],\n })),\n next_steps: response.next_steps,\n };\n },\n }),\n\n interpret: tool({\n description: INTERPRET_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(INTERPRET_QUERY_PARAM),\n items: z.array(z.array(z.string())).min(1).describe(INTERPRET_ITEMS_PARAM),\n }),\n execute: async ({ query, items }) => {\n const response = await client.retrieval.interpret(sandbox_id, {\n query,\n items,\n project_id,\n window,\n preset,\n });\n return { brief: response.brief };\n },\n }),\n\n search: tool({\n description: SEARCH_DESCRIPTION,\n inputSchema: z.object({\n query: z.string().describe(SEARCH_QUERY_PARAM),\n }),\n execute: async ({ query }) => {\n const response = await client.retrieval.search(sandbox_id, {\n query,\n project_id,\n window,\n preset,\n });\n return { answer: response.answer };\n },\n }),\n\n get_origin: tool({\n description: GET_ORIGIN_DESCRIPTION,\n inputSchema: z.object({\n canonical_ids: z\n .array(z.string())\n .min(1)\n .max(100)\n .describe(ORIGIN_CANONICAL_IDS_PARAM),\n limit_per_canonical: z\n .number()\n .int()\n .min(1)\n .max(50)\n .optional()\n .describe(ORIGIN_LIMIT_PARAM),\n }),\n execute: async ({ canonical_ids, limit_per_canonical }) => {\n const response = await client.retrieval.getOrigin(sandbox_id, {\n canonical_ids,\n ...(limit_per_canonical !== undefined ? { limit_per_canonical } : {}),\n });\n return {\n sandbox_id: response.sandbox_id,\n origins: response.origins.map((entry: OriginEntry) => ({\n canonical_id: entry.canonical_id,\n files: entry.files.map((f: OriginFile) => ({\n file_path: f.file_path,\n extraction_count: f.extraction_count,\n })),\n })),\n };\n },\n }),\n };\n}\n","import type { ChatMessage, ChatRole, ContextWindow } from '@copass/core';\n\n/**\n * Minimal shape of a Vercel AI SDK step finish event — typed structurally so\n * this module doesn't lock you to an `ai` major version.\n */\nexport interface StepFinishLike {\n response?: {\n messages?: Array<{\n role?: string;\n content?: unknown;\n }>;\n };\n}\n\nexport interface WindowTrackerOptions {\n /** The Context Window to mirror messages into. */\n window: ContextWindow;\n /**\n * Include `tool` messages (results returned by a tool call) as turns.\n * Default: false — tool results are usually retrieval noise.\n */\n includeToolMessages?: boolean;\n}\n\nexport interface WindowTracker {\n /**\n * Pass to Vercel AI SDK's `generateText` / `streamText` as `onStepFinish`.\n * Each step's assistant + tool output messages land in the window,\n * de-duplicated against what's already there.\n */\n onStepFinish: (step: StepFinishLike) => Promise<void>;\n /**\n * Record the user's turn before you call `generateText`. Vercel's\n * `onStepFinish` only surfaces messages *generated* during a step, so the\n * user's initial input has to be captured explicitly. Safe to call even if\n * the message was already added — the tracker de-duplicates.\n */\n recordUserTurn: (content: string) => Promise<void>;\n}\n\n/**\n * Build a window tracker for Vercel AI SDK agent loops.\n *\n * @example\n * ```ts\n * import { copassTools, createWindowTracker } from '@copass/ai-sdk';\n *\n * const tools = copassTools({ client, sandbox_id, window });\n * const tracker = createWindowTracker({ window });\n *\n * await tracker.recordUserTurn(userMessage);\n * const { text } = await generateText({\n * model,\n * tools,\n * onStepFinish: tracker.onStepFinish,\n * prompt: userMessage,\n * maxSteps: 5,\n * });\n * ```\n */\nexport function createWindowTracker(options: WindowTrackerOptions): WindowTracker {\n const { window, includeToolMessages = false } = options;\n const seen = new Set<string>();\n for (const turn of window.getTurns()) {\n seen.add(hashTurn(turn));\n }\n\n async function addIfNew(turn: ChatMessage): Promise<void> {\n if (!turn.content.trim()) return;\n const key = hashTurn(turn);\n if (seen.has(key)) return;\n seen.add(key);\n try {\n await window.addTurn(turn);\n } catch {\n // Best-effort — missing a turn still lets retrieval see prior ones.\n }\n }\n\n return {\n async onStepFinish(step) {\n const messages = step.response?.messages ?? [];\n for (const msg of messages) {\n const role = roleFromRole(msg.role, includeToolMessages);\n if (!role) continue;\n await addIfNew({ role, content: contentToString(msg.content) });\n }\n },\n recordUserTurn(content) {\n return addIfNew({ role: 'user', content });\n },\n };\n}\n\nfunction roleFromRole(role: string | undefined, includeToolMessages: boolean): ChatRole | null {\n switch (role) {\n case 'user':\n return 'user';\n case 'assistant':\n return 'assistant';\n case 'system':\n return 'system';\n case 'tool':\n return includeToolMessages ? 'system' : null;\n default:\n return null;\n }\n}\n\nfunction contentToString(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return String(content ?? '');\n return content\n .map((part) => {\n if (typeof part === 'string') return part;\n if (part && typeof part === 'object') {\n const rec = part as Record<string, unknown>;\n if (typeof rec.text === 'string') return rec.text;\n }\n return '';\n })\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction hashTurn(turn: ChatMessage): string {\n return `${turn.role}:${turn.content.slice(0, 500)}`;\n}\n"],"mappings":"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","names":[]} |
+3
-3
| { | ||
| "name": "@copass/ai-sdk", | ||
| "version": "0.5.9", | ||
| "version": "0.5.10", | ||
| "description": "Vercel AI SDK tool adapters for Copass — drop-in discover/interpret/search tools for LLM agents", | ||
@@ -67,3 +67,3 @@ "publishConfig": { | ||
| "devDependencies": { | ||
| "@copass/core": "^0.11.1", | ||
| "@copass/core": "^0.11.2", | ||
| "@types/node": "^25.0.0", | ||
@@ -75,3 +75,3 @@ "ai": "^6.0.0", | ||
| }, | ||
| "gitHead": "647e1abcdbdc52dae62913df5cf86aeaf68dc2ff" | ||
| "gitHead": "527f8b0ee2b7e2b30696923f81e22157bf6af8c9" | ||
| } |
55877
2.55%506
2.22%