diff --git a/packages/context/src/agent/agent-tool.ts b/packages/context/src/agent/agent-tool.ts index e454f8b7..294b7c12 100644 --- a/packages/context/src/agent/agent-tool.ts +++ b/packages/context/src/agent/agent-tool.ts @@ -5,7 +5,7 @@ export interface AgentToolCallOptions { toolCallId?: string; } -export type AgentToolOutput = string | { markdown: string; structured?: unknown }; +export type AgentToolOutput = string | { markdown: string; structured?: unknown } | Record; export interface AgentToolDefinition = ZodObject> { name: string; @@ -35,7 +35,13 @@ export function assertAgentToolSet(toolSet: AgentToolSet): void { export function agentToolOutputToText(output: AgentToolOutput): string { if (output && typeof output === 'object' && 'markdown' in output) { - return output.markdown; + const markdown = output.markdown; + if (typeof markdown === 'string') { + return markdown; + } + } + if (output && typeof output === 'object') { + return JSON.stringify(output); } return String(output); } diff --git a/packages/context/src/ingest/adapters/historic-sql/evidence-tool.ts b/packages/context/src/ingest/adapters/historic-sql/evidence-tool.ts index 29d66cb2..21c447f4 100644 --- a/packages/context/src/ingest/adapters/historic-sql/evidence-tool.ts +++ b/packages/context/src/ingest/adapters/historic-sql/evidence-tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../../agent/index.js'; import { historicSqlEvidencePath, serializeHistoricSqlEvidence } from './evidence.js'; import { patternOutputSchema, tableUsageOutputSchema } from './skill-schemas.js'; @@ -91,12 +91,15 @@ function evidenceEnvelope(input: EmitHistoricSqlEvidenceInput, connectionId: str } export function createEmitHistoricSqlEvidenceTool(defaultContext?: EmitHistoricSqlEvidenceToolContext) { - return tool({ + return createAgentTool({ + name: 'emit_historic_sql_evidence', description: 'Record typed historic-SQL evidence for deterministic projection. Use this instead of wiki_write, sl_write_source, sl_edit_source, or context_candidate_write during historic-SQL WorkUnits.', inputSchema: emitHistoricSqlEvidenceInputSchema, execute: async (input, options): Promise => { - const context = (options.experimental_context as EmitHistoricSqlEvidenceToolContext | undefined) ?? defaultContext; + const context = + ((options as { experimental_context?: EmitHistoricSqlEvidenceToolContext }).experimental_context ?? + defaultContext); const ingest = context?.session?.ingest; const configService = context?.session?.configService; if (!ingest || ingest.sourceKey !== 'historic-sql' || !configService || !context?.connectionId) { diff --git a/packages/context/src/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts b/packages/context/src/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts index 67bb68c6..1da40339 100644 --- a/packages/context/src/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts +++ b/packages/context/src/ingest/adapters/looker/tools/looker-query-to-sl.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../../../agent/index.js'; import type { ToolOutput } from '../../../../tools/index.js'; import { type ParsedTargetTable, stagedLookerQuerySchema } from '../types.js'; @@ -160,7 +160,8 @@ export function buildLookerSlProposal(raw: LookerQueryToSlInput): LookerSlPropos } export function createLookerQueryToSlTool() { - return tool({ + return createAgentTool({ + name: 'looker_query_to_sl', description: 'Given one staged Looker query JSON, return a conservative proposal for SL measures, dimensions, reusable filters, and triage priority. The proposal is advisory; verify with SL tools before writing.', inputSchema: lookerQueryToSlInputSchema, @@ -171,13 +172,6 @@ export function createLookerQueryToSlTool() { structured, }; }, - toModelOutput: ({ output }) => { - const markdown = - output && typeof output === 'object' && 'markdown' in output - ? String((output as { markdown: unknown }).markdown) - : String(output); - return { type: 'content', value: [{ type: 'text', text: markdown }] }; - }, }); } diff --git a/packages/context/src/ingest/context-candidates/curator-pagination.service.ts b/packages/context/src/ingest/context-candidates/curator-pagination.service.ts index d40bef9b..95ba5a1b 100644 --- a/packages/context/src/ingest/context-candidates/curator-pagination.service.ts +++ b/packages/context/src/ingest/context-candidates/curator-pagination.service.ts @@ -1,6 +1,5 @@ import type { KtxModelRole } from '@ktx/llm'; -import type { ToolSet } from 'ai'; -import type { AgentRunnerService } from '../../agent/index.js'; +import type { AgentRunnerService, AgentToolSet } from '../../agent/index.js'; import { type KtxLogger, noopLogger } from '../../core/index.js'; import type { MemoryAction } from '../../memory/index.js'; import type { ContextCandidateForDedup, CuratorPaginationPort, CuratorPaginationReport } from '../ports.js'; @@ -38,7 +37,7 @@ export interface CuratorPaginationInput { modelRole: KtxModelRole; buildSystemPrompt: () => string; buildUserPrompt: (input: CuratorPaginationPromptInput) => string; - buildToolSet: (passNumber: number) => ToolSet; + buildToolSet: (passNumber: number) => AgentToolSet; getReconciliationActions: () => MemoryAction[]; onStepFinish?: (info: { passNumber: number; stepIndex: number; stepBudget: number }) => void; } diff --git a/packages/context/src/ingest/ingest-bundle.runner.test.ts b/packages/context/src/ingest/ingest-bundle.runner.test.ts index c73eb436..348eb591 100644 --- a/packages/context/src/ingest/ingest-bundle.runner.test.ts +++ b/packages/context/src/ingest/ingest-bundle.runner.test.ts @@ -200,7 +200,7 @@ const makeDeps = () => { const slValidator = { validateSingleSource: vi.fn().mockResolvedValue({ errors: [], warnings: [] }) }; const toolsetFactory = { createIngestWuToolset: vi.fn().mockReturnValue({ - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), getToolNames: vi.fn().mockReturnValue([]), }), @@ -419,7 +419,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { deps.toolsetFactory.createIngestWuToolset.mockImplementation((toolSession: any) => { sessions.push(toolSession); return { - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), getToolNames: vi.fn().mockReturnValue([]), }; @@ -591,7 +591,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { deps.toolsetFactory.createIngestWuToolset.mockImplementation((toolSession: any) => { currentToolSession = toolSession; return { - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), getToolNames: vi.fn().mockReturnValue([]), }; @@ -663,7 +663,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { deps.toolsetFactory.createIngestWuToolset.mockImplementation((toolSession: any) => { currentToolSession = toolSession; return { - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), getToolNames: vi.fn().mockReturnValue([]), }; @@ -834,7 +834,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { it('stores memory-flow provenance and transcript summaries in the ingest report body', async () => { const deps = makeDeps(); deps.toolsetFactory.createIngestWuToolset.mockReturnValue({ - toAiSdkTools: vi.fn().mockReturnValue({ + toAgentTools: vi.fn().mockReturnValue({ read_raw_span: { description: 'read a raw span', inputSchema: {}, @@ -1376,7 +1376,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { deps.toolsetFactory.createIngestWuToolset.mockImplementation((toolSession: any) => { currentToolSession = toolSession; return { - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), getToolNames: vi.fn().mockReturnValue([]), }; @@ -1933,7 +1933,7 @@ describe('IngestBundleRunner — Stages 1 → 7', () => { deps.toolsetFactory.createIngestWuToolset.mockImplementation((toolSession: any) => { currentToolSession = toolSession; return { - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), getToolNames: vi.fn().mockReturnValue([]), }; diff --git a/packages/context/src/ingest/ingest-bundle.runner.ts b/packages/context/src/ingest/ingest-bundle.runner.ts index 33495736..52fd33f0 100644 --- a/packages/context/src/ingest/ingest-bundle.runner.ts +++ b/packages/context/src/ingest/ingest-bundle.runner.ts @@ -1,8 +1,8 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; -import { type Tool, tool } from 'ai'; import pLimit from 'p-limit'; import { z } from 'zod'; +import { createAgentTool, type AgentToolSet } from '../agent/index.js'; import { type KtxLogger, noopLogger } from '../core/index.js'; import type { CaptureSession, MemoryAction } from '../memory/index.js'; import type { SemanticLayerService, SemanticLayerSource, SlValidationDeps } from '../sl/index.js'; @@ -694,8 +694,9 @@ export class IngestBundleRunner { }; const skillsLoadedPerWu: string[] = []; - const loadSkillTool: Record = { - load_skill: tool({ + const loadSkillTool: AgentToolSet = { + load_skill: createAgentTool({ + name: 'load_skill', description: 'Load a skill to get specialized instructions. Call this when a skill listed in the system prompt matches the current task.', inputSchema: z.object({ name: z.string() }), @@ -765,7 +766,7 @@ export class IngestBundleRunner { wu: wuInner, loadSkillTool, emitUnmappedFallbackTool: wuEmitUnmappedFallbackTool, - toolsetTools: wuToolset.toAiSdkTools(wuToolContext), + toolsetTools: wuToolset.toAgentTools(wuToolContext), }), join(transcriptDir, `${wuInner.unitKey}.jsonl`), wuInner.unitKey, @@ -921,8 +922,9 @@ export class IngestBundleRunner { ingest: ingestToolMetadata, session: rcToolSession, }; - const rcLoadSkill: Record = { - load_skill: tool({ + const rcLoadSkill: AgentToolSet = { + load_skill: createAgentTool({ + name: 'load_skill', description: 'Load a skill.', inputSchema: z.object({ name: z.string() }), execute: async ({ name }) => { @@ -1026,7 +1028,7 @@ export class IngestBundleRunner { emitArtifactResolutionTool: rcEmitArtifactResolutionTool, emitUnmappedFallbackTool: rcEmitUnmappedFallbackTool, readRawSpanTool: rcRawSpanTool, - toolsetTools: rcToolset.toAiSdkTools(rcToolContext), + toolsetTools: rcToolset.toAgentTools(rcToolContext), }), join(transcriptDir, 'reconcile.jsonl'), 'reconcile', @@ -1075,7 +1077,7 @@ export class IngestBundleRunner { emitArtifactResolutionTool: rcEmitArtifactResolutionTool, emitUnmappedFallbackTool: rcEmitUnmappedFallbackTool, readRawSpanTool: rcRawSpanTool, - toolsetTools: rcToolset.toAiSdkTools(rcToolContext), + toolsetTools: rcToolset.toAgentTools(rcToolContext), }), join(transcriptDir, 'reconcile.jsonl'), 'reconcile', diff --git a/packages/context/src/ingest/local-bundle-runtime.ts b/packages/context/src/ingest/local-bundle-runtime.ts index 3372e7b8..8165deee 100644 --- a/packages/context/src/ingest/local-bundle-runtime.ts +++ b/packages/context/src/ingest/local-bundle-runtime.ts @@ -540,7 +540,7 @@ class LocalIngestToolsetFactory implements IngestToolsetFactoryPort { } createIngestWuToolset(session: ToolSession, options?: { includeContextEvidenceTools?: boolean }): IngestToolsetLike { - const sourceTools: Record = + const sourceTools: AgentToolSet = session.ingest?.sourceKey === 'historic-sql' ? { emit_historic_sql_evidence: createEmitHistoricSqlEvidenceTool({ diff --git a/packages/context/src/ingest/ports.ts b/packages/context/src/ingest/ports.ts index d6f93211..a1b75bc0 100644 --- a/packages/context/src/ingest/ports.ts +++ b/packages/context/src/ingest/ports.ts @@ -314,7 +314,7 @@ export interface CuratorPaginationPort { items: ReconcileCandidateForPrompt[]; runState: ReconcilePromptRunState; }) => string; - buildToolSet: (passNumber: number) => ToolSet; + buildToolSet: (passNumber: number) => AgentToolSet; getReconciliationActions: () => MemoryAction[]; onStepFinish?: (info: { passNumber: number; stepIndex: number; stepBudget: number }) => void; }): Promise; diff --git a/packages/context/src/ingest/stages/build-reconcile-context.test.ts b/packages/context/src/ingest/stages/build-reconcile-context.test.ts index 9ac95356..b83d0102 100644 --- a/packages/context/src/ingest/stages/build-reconcile-context.test.ts +++ b/packages/context/src/ingest/stages/build-reconcile-context.test.ts @@ -1,6 +1,16 @@ import { describe, expect, it, vi } from 'vitest'; +import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import { buildReconcileSystemPrompt, buildReconcileToolSet, buildReconcileUserPrompt } from './build-reconcile-context.js'; +const fakeTool = (name: string) => + createAgentTool({ + name, + description: name, + inputSchema: z.object({}), + execute: async () => `${name} output`, + }); + describe('buildReconcileSystemPrompt', () => { it('appends canonical pins when relevant pins are supplied', () => { const prompt = buildReconcileSystemPrompt({ @@ -76,26 +86,16 @@ describe('buildReconcileUserPrompt', () => { describe('buildReconcileToolSet', () => { it('includes emit_unmapped_fallback with the reconciliation tools', () => { const toolSet = buildReconcileToolSet({ - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - stageListTool: { stage_list: { description: 'stage list', inputSchema: {} as any, execute: vi.fn() } } as any, - stageDiffTool: { stage_diff: { description: 'stage diff', inputSchema: {} as any, execute: vi.fn() } } as any, - evictionListTool: { - eviction_list: { description: 'eviction list', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitConflictResolutionTool: { - emit_conflict_resolution: { description: 'conflict', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitEvictionDecisionTool: { - emit_eviction_decision: { description: 'eviction', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitArtifactResolutionTool: { - emit_artifact_resolution: { description: 'resolution', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, - readRawSpanTool: { read_raw_span: { description: 'raw span', inputSchema: {} as any, execute: vi.fn() } } as any, - toolsetTools: { sl_write_source: {} as any, wiki_write: {} as any }, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + stageListTool: { stage_list: fakeTool('stage_list') }, + stageDiffTool: { stage_diff: fakeTool('stage_diff') }, + evictionListTool: { eviction_list: fakeTool('eviction_list') }, + emitConflictResolutionTool: { emit_conflict_resolution: fakeTool('emit_conflict_resolution') }, + emitEvictionDecisionTool: { emit_eviction_decision: fakeTool('emit_eviction_decision') }, + emitArtifactResolutionTool: { emit_artifact_resolution: fakeTool('emit_artifact_resolution') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, + readRawSpanTool: { read_raw_span: fakeTool('read_raw_span') }, + toolsetTools: { sl_write_source: fakeTool('sl_write_source'), wiki_write: fakeTool('wiki_write') }, }); expect(Object.keys(toolSet).sort()).toEqual( @@ -114,31 +114,30 @@ describe('buildReconcileToolSet', () => { 'wiki_write', ].sort(), ); + expect(toolSet.record_verification_ledger.inputSchema).toBeInstanceOf(z.ZodObject); + expect(toolSet.emit_conflict_resolution.name).toBe('emit_conflict_resolution'); }); it('requires the verification ledger before reconciliation write tools run', async () => { const slWrite = vi.fn().mockResolvedValue({ markdown: 'written', structured: { success: true } }); const toolSet = buildReconcileToolSet({ - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - stageListTool: { stage_list: { description: 'stage list', inputSchema: {} as any, execute: vi.fn() } } as any, - stageDiffTool: { stage_diff: { description: 'stage diff', inputSchema: {} as any, execute: vi.fn() } } as any, - evictionListTool: { - eviction_list: { description: 'eviction list', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitConflictResolutionTool: { - emit_conflict_resolution: { description: 'conflict', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitEvictionDecisionTool: { - emit_eviction_decision: { description: 'eviction', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitArtifactResolutionTool: { - emit_artifact_resolution: { description: 'resolution', inputSchema: {} as any, execute: vi.fn() }, - } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, - readRawSpanTool: { read_raw_span: { description: 'raw span', inputSchema: {} as any, execute: vi.fn() } } as any, - toolsetTools: { sl_write_source: { description: 'sl write', inputSchema: {} as any, execute: slWrite } as any }, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + stageListTool: { stage_list: fakeTool('stage_list') }, + stageDiffTool: { stage_diff: fakeTool('stage_diff') }, + evictionListTool: { eviction_list: fakeTool('eviction_list') }, + emitConflictResolutionTool: { emit_conflict_resolution: fakeTool('emit_conflict_resolution') }, + emitEvictionDecisionTool: { emit_eviction_decision: fakeTool('emit_eviction_decision') }, + emitArtifactResolutionTool: { emit_artifact_resolution: fakeTool('emit_artifact_resolution') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, + readRawSpanTool: { read_raw_span: fakeTool('read_raw_span') }, + toolsetTools: { + sl_write_source: createAgentTool({ + name: 'sl_write_source', + description: 'sl write', + inputSchema: z.object({ connectionId: z.string(), sourceName: z.string() }), + execute: slWrite, + }), + }, }); const correction = await toolSet.sl_write_source.execute?.( diff --git a/packages/context/src/ingest/stages/build-reconcile-context.ts b/packages/context/src/ingest/stages/build-reconcile-context.ts index 9533acbd..b56c250e 100644 --- a/packages/context/src/ingest/stages/build-reconcile-context.ts +++ b/packages/context/src/ingest/stages/build-reconcile-context.ts @@ -1,4 +1,4 @@ -import type { Tool, ToolSet } from 'ai'; +import type { AgentToolSet } from '../../agent/index.js'; import { buildCanonicalPinsPromptBlock, type CanonicalPin } from '../canonical-pins.js'; import { createVerificationLedgerState, @@ -181,19 +181,19 @@ export function buildReconcileUserPrompt( } export interface ReconcileToolSetInput { - loadSkillTool: Record; - stageListTool: Record; - stageDiffTool: Record; - evictionListTool: Record; - emitConflictResolutionTool: Record; - emitEvictionDecisionTool: Record; - emitArtifactResolutionTool: Record; - emitUnmappedFallbackTool: Record; - readRawSpanTool: Record; - toolsetTools: ToolSet; + loadSkillTool: AgentToolSet; + stageListTool: AgentToolSet; + stageDiffTool: AgentToolSet; + evictionListTool: AgentToolSet; + emitConflictResolutionTool: AgentToolSet; + emitEvictionDecisionTool: AgentToolSet; + emitArtifactResolutionTool: AgentToolSet; + emitUnmappedFallbackTool: AgentToolSet; + readRawSpanTool: AgentToolSet; + toolsetTools: AgentToolSet; } -export function buildReconcileToolSet(input: ReconcileToolSetInput): ToolSet { +export function buildReconcileToolSet(input: ReconcileToolSetInput): AgentToolSet { const state = createVerificationLedgerState(); return withVerificationLedger( { diff --git a/packages/context/src/ingest/stages/build-wu-context.test.ts b/packages/context/src/ingest/stages/build-wu-context.test.ts index db17154e..fc3d3cd5 100644 --- a/packages/context/src/ingest/stages/build-wu-context.test.ts +++ b/packages/context/src/ingest/stages/build-wu-context.test.ts @@ -1,6 +1,16 @@ import { describe, expect, it, vi } from 'vitest'; +import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import { buildWuSystemPrompt, buildWuToolSet, buildWuUserPrompt } from './build-wu-context.js'; +const fakeTool = (name: string) => + createAgentTool({ + name, + description: name, + inputSchema: z.object({}), + execute: async () => `${name} output`, + }); + describe('buildWuUserPrompt', () => { it('includes rawFiles, dependencyPaths, peerFileIndex, and priorProvenance when present', () => { const prompt = buildWuUserPrompt({ @@ -56,11 +66,9 @@ describe('buildWuToolSet', () => { const toolSet = buildWuToolSet({ stagedDir: '/tmp/staged', wu: { unitKey: 'u1', rawFiles: ['a.yml'], peerFileIndex: [], dependencyPaths: ['dep.yml'] }, - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, - toolsetTools: { wiki_search: {} as any, sl_write_source: {} as any }, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, + toolsetTools: { wiki_write: fakeTool('wiki_write') }, }); expect(Object.keys(toolSet).sort()).toEqual( [ @@ -69,10 +77,11 @@ describe('buildWuToolSet', () => { 'read_raw_file', 'read_raw_span', 'record_verification_ledger', - 'sl_write_source', - 'wiki_search', + 'wiki_write', ].sort(), ); + expect(toolSet.record_verification_ledger.inputSchema).toBeInstanceOf(z.ZodObject); + expect(toolSet.wiki_write.name).toBe('wiki_write'); }); it('requires the verification ledger before write-capable tools run', async () => { @@ -80,11 +89,16 @@ describe('buildWuToolSet', () => { const toolSet = buildWuToolSet({ stagedDir: '/tmp/staged', wu: { unitKey: 'u1', rawFiles: ['a.yml'], peerFileIndex: [], dependencyPaths: [] }, - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, - toolsetTools: { wiki_write: { description: 'write', inputSchema: {} as any, execute: wikiWrite } as any }, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, + toolsetTools: { + wiki_write: createAgentTool({ + name: 'wiki_write', + description: 'write', + inputSchema: z.object({ key: z.string() }), + execute: wikiWrite, + }), + }, }); const correction = await toolSet.wiki_write.execute?.({ key: 'customer-rules' }, { toolCallId: 't1' } as any); @@ -112,11 +126,9 @@ describe('buildWuToolSet', () => { sourceKey: 'looker', stagedDir: '/tmp/staged', wu: { unitKey: 'looker-look-20', rawFiles: ['looks/20.json'], peerFileIndex: [], dependencyPaths: [] }, - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, - toolsetTools: { wiki_search: {} as any, sl_write_source: {} as any }, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, + toolsetTools: { wiki_search: fakeTool('wiki_search'), sl_write_source: fakeTool('sl_write_source') }, }); expect(Object.keys(toolSet).sort()).toEqual( @@ -138,11 +150,9 @@ describe('buildWuToolSet', () => { sourceKey: 'metabase', stagedDir: '/tmp/staged', wu: { unitKey: 'metabase-col-1', rawFiles: ['cards/1.json'], peerFileIndex: [], dependencyPaths: [] }, - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, - toolsetTools: { wiki_search: {} as any, sl_write_source: {} as any }, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, + toolsetTools: { wiki_search: fakeTool('wiki_search'), sl_write_source: fakeTool('sl_write_source') }, }); expect(Object.keys(toolSet)).not.toContain('looker_query_to_sl'); @@ -160,15 +170,13 @@ describe('buildWuToolSet', () => { slDisallowed: true, slDisallowedReason: 'lookml_connection_mismatch', }, - loadSkillTool: { load_skill: { description: 'load', inputSchema: {} as any, execute: vi.fn() } } as any, - emitUnmappedFallbackTool: { - emit_unmapped_fallback: { description: 'fallback', inputSchema: {} as any, execute: vi.fn() }, - } as any, + loadSkillTool: { load_skill: fakeTool('load_skill') }, + emitUnmappedFallbackTool: { emit_unmapped_fallback: fakeTool('emit_unmapped_fallback') }, toolsetTools: { - sl_write_source: {} as any, - sl_edit_source: {} as any, - sl_read_source: {} as any, - wiki_search: {} as any, + sl_write_source: fakeTool('sl_write_source'), + sl_edit_source: fakeTool('sl_edit_source'), + sl_read_source: fakeTool('sl_read_source'), + wiki_search: fakeTool('wiki_search'), }, }); diff --git a/packages/context/src/ingest/stages/build-wu-context.ts b/packages/context/src/ingest/stages/build-wu-context.ts index bfa1bd9c..b8ddb5a1 100644 --- a/packages/context/src/ingest/stages/build-wu-context.ts +++ b/packages/context/src/ingest/stages/build-wu-context.ts @@ -1,4 +1,4 @@ -import type { Tool, ToolSet } from 'ai'; +import type { AgentToolSet } from '../../agent/index.js'; import { buildCanonicalPinsPromptBlock, type CanonicalPin } from '../canonical-pins.js'; import { createLookerQueryToSlTool } from '../adapters/looker/tools/looker-query-to-sl.tool.js'; import type { IngestProvenanceRow } from '../ports.js'; @@ -88,12 +88,12 @@ export interface BuildWuToolSetInput { sourceKey?: string; stagedDir: string; wu: WorkUnit; - loadSkillTool: Record; - emitUnmappedFallbackTool: Record; - toolsetTools: ToolSet; + loadSkillTool: AgentToolSet; + emitUnmappedFallbackTool: AgentToolSet; + toolsetTools: AgentToolSet; } -function withoutWriteSlTools(toolset: ToolSet, wu: WorkUnit): ToolSet { +function withoutWriteSlTools(toolset: AgentToolSet, wu: WorkUnit): AgentToolSet { if (!wu.slDisallowed) { return toolset; } @@ -103,9 +103,10 @@ function withoutWriteSlTools(toolset: ToolSet, wu: WorkUnit): ToolSet { return next; } -export function buildWuToolSet(input: BuildWuToolSetInput): ToolSet { +export function buildWuToolSet(input: BuildWuToolSetInput): AgentToolSet { const allowedPaths = new Set([...input.wu.rawFiles, ...input.wu.dependencyPaths]); - const lookerTools: ToolSet = input.sourceKey === 'looker' ? { looker_query_to_sl: createLookerQueryToSlTool() } : {}; + const lookerTools: AgentToolSet = + input.sourceKey === 'looker' ? { looker_query_to_sl: createLookerQueryToSlTool() } : {}; const state = createVerificationLedgerState(); return withVerificationLedger( withoutWriteSlTools( diff --git a/packages/context/src/ingest/stages/stage-3-work-units.ts b/packages/context/src/ingest/stages/stage-3-work-units.ts index b6e64f86..3e5c7405 100644 --- a/packages/context/src/ingest/stages/stage-3-work-units.ts +++ b/packages/context/src/ingest/stages/stage-3-work-units.ts @@ -1,6 +1,5 @@ -import type { AgentRunnerService } from '@ktx/context/agent'; +import type { AgentRunnerService, AgentToolSet } from '@ktx/context/agent'; import type { KtxModelRole } from '@ktx/llm'; -import type { Tool } from 'ai'; import type { CaptureSession, MemoryAction } from '../../memory/index.js'; import { listTouchedSlSources, type TouchedSlSource } from '../../tools/index.js'; import type { WorkUnit } from '../types.js'; @@ -19,7 +18,7 @@ export interface WorkUnitExecutionDeps { resetHardTo: (targetSha: string) => Promise; buildSystemPrompt: (wu: WorkUnit) => string; buildUserPrompt: (wu: WorkUnit) => string; - buildToolSet: (wu: WorkUnit) => Record; + buildToolSet: (wu: WorkUnit) => AgentToolSet; captureSession: CaptureSession; sessionActions: MemoryAction[]; modelRole: KtxModelRole; diff --git a/packages/context/src/ingest/stages/stage-4-reconciliation.ts b/packages/context/src/ingest/stages/stage-4-reconciliation.ts index 00bb2d2c..0f43f3c4 100644 --- a/packages/context/src/ingest/stages/stage-4-reconciliation.ts +++ b/packages/context/src/ingest/stages/stage-4-reconciliation.ts @@ -1,6 +1,5 @@ -import type { AgentRunnerService } from '@ktx/context/agent'; +import type { AgentRunnerService, AgentToolSet } from '@ktx/context/agent'; import type { KtxModelRole } from '@ktx/llm'; -import type { ToolSet } from 'ai'; import type { EvictionUnit } from '../types.js'; import type { StageIndex } from './stage-index.types.js'; @@ -10,7 +9,7 @@ export interface ReconciliationContext { agentRunner: AgentRunnerService; buildSystemPrompt: (idx: StageIndex, ev: EvictionUnit | undefined) => string; buildUserPrompt: (idx: StageIndex, ev: EvictionUnit | undefined) => string; - buildToolSet: () => ToolSet; + buildToolSet: () => AgentToolSet; modelRole: KtxModelRole; stepBudget: number; sourceKey: string; diff --git a/packages/context/src/ingest/tools/emit-artifact-resolution.tool.ts b/packages/context/src/ingest/tools/emit-artifact-resolution.tool.ts index 06ac2cae..fa26b0af 100644 --- a/packages/context/src/ingest/tools/emit-artifact-resolution.tool.ts +++ b/packages/context/src/ingest/tools/emit-artifact-resolution.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import type { ArtifactResolutionRecord, StageIndex } from '../stages/stage-index.types.js'; interface EmitArtifactResolutionDeps { @@ -17,7 +17,8 @@ function sameArtifactResolution(left: ArtifactResolutionRecord, right: ArtifactR } export function createEmitArtifactResolutionTool(deps: EmitArtifactResolutionDeps) { - return tool({ + return createAgentTool({ + name: 'emit_artifact_resolution', description: 'Record one explicit artifact resolution for ingest provenance. Use when reconciliation merges or subsumes an artifact without creating a new wiki or SL write action.', inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/emit-conflict-resolution.tool.ts b/packages/context/src/ingest/tools/emit-conflict-resolution.tool.ts index b25e1395..d2976129 100644 --- a/packages/context/src/ingest/tools/emit-conflict-resolution.tool.ts +++ b/packages/context/src/ingest/tools/emit-conflict-resolution.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import type { ConflictResolvedRecord, StageIndex } from '../stages/stage-index.types.js'; interface EmitConflictResolutionDeps { @@ -7,7 +7,8 @@ interface EmitConflictResolutionDeps { } export function createEmitConflictResolutionTool(deps: EmitConflictResolutionDeps) { - return tool({ + return createAgentTool({ + name: 'emit_conflict_resolution', description: 'Record one conflict resolution decision for the final IngestReport. Call after resolving or flagging a cross-WorkUnit conflict.', inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/emit-eviction-decision.tool.ts b/packages/context/src/ingest/tools/emit-eviction-decision.tool.ts index 28a32a5b..2f8dd026 100644 --- a/packages/context/src/ingest/tools/emit-eviction-decision.tool.ts +++ b/packages/context/src/ingest/tools/emit-eviction-decision.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import type { EvictionAppliedRecord, StageIndex } from '../stages/stage-index.types.js'; interface EmitEvictionDecisionDeps { @@ -15,7 +15,8 @@ function sameEvictionArtifact(left: EvictionAppliedRecord, right: EvictionApplie export function createEmitEvictionDecisionTool(deps: EmitEvictionDecisionDeps) { const allowedPaths = new Set(deps.deletedRawPaths); - return tool({ + return createAgentTool({ + name: 'emit_eviction_decision', description: 'Record one eviction decision for the final IngestReport. The rawPath must come from the current Eviction Set.', inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/emit-reconciliation-records.tool.test.ts b/packages/context/src/ingest/tools/emit-reconciliation-records.tool.test.ts index 1cd77514..3afe96ed 100644 --- a/packages/context/src/ingest/tools/emit-reconciliation-records.tool.test.ts +++ b/packages/context/src/ingest/tools/emit-reconciliation-records.tool.test.ts @@ -1,5 +1,5 @@ -import type { Tool } from 'ai'; import { describe, expect, it } from 'vitest'; +import type { AgentToolDefinition } from '../../agent/index.js'; import type { StageIndex } from '../stages/stage-index.types.js'; import { createEmitArtifactResolutionTool } from './emit-artifact-resolution.tool.js'; import { createEmitConflictResolutionTool } from './emit-conflict-resolution.tool.js'; @@ -17,11 +17,8 @@ function makeStageIndex(): StageIndex { }; } -async function executeTool(tool: Tool, input: NoInfer) { - if (!tool.execute) { - throw new Error('tool is not executable'); - } - return (await tool.execute(input, { toolCallId: 'tool-call-1', messages: [] })) as string; +async function executeTool(tool: AgentToolDefinition, input: NoInfer) { + return (await tool.execute(input, { toolCallId: 'tool-call-1' })) as string; } describe('reconciliation emit tools', () => { diff --git a/packages/context/src/ingest/tools/emit-unmapped-fallback.tool.ts b/packages/context/src/ingest/tools/emit-unmapped-fallback.tool.ts index cdb1a483..aee70273 100644 --- a/packages/context/src/ingest/tools/emit-unmapped-fallback.tool.ts +++ b/packages/context/src/ingest/tools/emit-unmapped-fallback.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import type { StageIndex, UnmappedFallbackRecord, UnmappedFallbackReason } from '../stages/stage-index.types.js'; interface EmitUnmappedFallbackDeps { @@ -61,7 +61,8 @@ function requiresMissingTableValidation(reason: UnmappedFallbackReason): boolean } export function createEmitUnmappedFallbackTool(deps: EmitUnmappedFallbackDeps) { - return tool({ + return createAgentTool({ + name: 'emit_unmapped_fallback', description: 'Record one unmapped fallback decision for the final IngestReport. The rawPath must be available to the current ingest stage. The tool generates the canonical detail from the structured reason and optional tableRef; use clarification only to add context that does not contradict the reason code.', inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/eviction-list.tool.ts b/packages/context/src/ingest/tools/eviction-list.tool.ts index 6a34d48f..f5fc532a 100644 --- a/packages/context/src/ingest/tools/eviction-list.tool.ts +++ b/packages/context/src/ingest/tools/eviction-list.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import type { IngestProvenancePort } from '../ports.js'; export interface EvictionListDeps { @@ -10,7 +10,8 @@ export interface EvictionListDeps { } export function createEvictionListTool(deps: EvictionListDeps) { - return tool({ + return createAgentTool({ + name: 'eviction_list', description: 'List every artifact that the most recent completed sync produced from a now-deleted raw file. Remove each listed artifact and record the decision with emit_eviction_decision so the ingest report lists every deleted-source decision.', inputSchema: z.object({}), diff --git a/packages/context/src/ingest/tools/read-raw-file.tool.ts b/packages/context/src/ingest/tools/read-raw-file.tool.ts index 4f84f28d..50d47e64 100644 --- a/packages/context/src/ingest/tools/read-raw-file.tool.ts +++ b/packages/context/src/ingest/tools/read-raw-file.tool.ts @@ -1,7 +1,7 @@ import { readFile, stat } from 'node:fs/promises'; import { join, normalize, resolve } from 'node:path'; -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; interface ReadRawFileDeps { stagedDir: string; @@ -12,7 +12,8 @@ const MAX_READ_RAW_FILE_BYTES = 120_000; export function createReadRawFileTool(deps: ReadRawFileDeps) { const stagedRoot = resolve(deps.stagedDir); - return tool({ + return createAgentTool({ + name: 'read_raw_file', description: "Read the full text content of a raw source file inside this WorkUnit. `path` must be relative to the staged bundle root (no leading slash, no `..`) and must appear in the WorkUnit's rawFiles or dependencyPaths list.", inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/read-raw-span.tool.ts b/packages/context/src/ingest/tools/read-raw-span.tool.ts index 21da54d1..54b48ef8 100644 --- a/packages/context/src/ingest/tools/read-raw-span.tool.ts +++ b/packages/context/src/ingest/tools/read-raw-span.tool.ts @@ -1,7 +1,7 @@ import { readFile } from 'node:fs/promises'; import { join, normalize, resolve } from 'node:path'; -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; interface ReadRawSpanDeps { stagedDir: string; @@ -10,7 +10,8 @@ interface ReadRawSpanDeps { export function createReadRawSpanTool(deps: ReadRawSpanDeps) { const stagedRoot = resolve(deps.stagedDir); - return tool({ + return createAgentTool({ + name: 'read_raw_span', description: 'Read a 1-based inclusive line range from a raw source file. Use this to resolve a provenance pointer like `file.lkml#L15-28` without loading the whole file into context.', inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/stage-diff.tool.ts b/packages/context/src/ingest/tools/stage-diff.tool.ts index f1cfc1a0..cb976648 100644 --- a/packages/context/src/ingest/tools/stage-diff.tool.ts +++ b/packages/context/src/ingest/tools/stage-diff.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import { memoryActionIdentity } from '../action-identity.js'; import type { StageIndex } from '../stages/stage-index.types.js'; @@ -8,7 +8,8 @@ export interface StageDiffDeps { } export function createStageDiffTool(deps: StageDiffDeps) { - return tool({ + return createAgentTool({ + name: 'stage_diff', description: 'Compare two WorkUnits by their writes. SL writes overlap only when target connection and artifact key both match; same-key SL actions on different target connections are non-overlapping.', inputSchema: z.object({ diff --git a/packages/context/src/ingest/tools/stage-list.tool.ts b/packages/context/src/ingest/tools/stage-list.tool.ts index bbdffc41..f879d64e 100644 --- a/packages/context/src/ingest/tools/stage-list.tool.ts +++ b/packages/context/src/ingest/tools/stage-list.tool.ts @@ -1,5 +1,5 @@ -import { tool } from 'ai'; import { z } from 'zod'; +import { createAgentTool } from '../../agent/index.js'; import type { StageIndex } from '../stages/stage-index.types.js'; export interface StageListDeps { @@ -11,7 +11,8 @@ function formatActionDetail(detail: string): string { } export function createStageListTool(deps: StageListDeps) { - return tool({ + return createAgentTool({ + name: 'stage_list', description: 'List every write made by Stage 3 WorkUnits in this job. Each entry has the unitKey, raw files, and the action set (SL sources touched, wiki pages written).', inputSchema: z.object({}), diff --git a/packages/context/src/ingest/tools/tool-call-logger.ts b/packages/context/src/ingest/tools/tool-call-logger.ts index 5a4aefde..8f351392 100644 --- a/packages/context/src/ingest/tools/tool-call-logger.ts +++ b/packages/context/src/ingest/tools/tool-call-logger.ts @@ -1,6 +1,6 @@ import { appendFile, mkdir } from 'node:fs/promises'; import { dirname } from 'node:path'; -import type { ToolExecuteFunction, ToolExecutionOptions, ToolSet } from 'ai'; +import type { AgentToolCallOptions, AgentToolSet } from '../../agent/index.js'; export interface ToolCallLogEntry { ts: string; @@ -31,7 +31,7 @@ interface ToolCallLoggerOptions { * sequential (`generateText` awaits each tool result), so per-WU files are * effectively single-writer and lines land in call order. */ -export function wrapToolsWithLogger( +export function wrapToolsWithLogger( tools: T, logFilePath: string, wuKey: string, @@ -44,13 +44,10 @@ export function wrapToolsWithLogger( wrapped[name] = original; continue; } - const wrappedExecute: ToolExecuteFunction = async ( - input: unknown, - opts: ToolExecutionOptions, - ) => { + const wrappedExecute = async (input: never, opts: AgentToolCallOptions) => { const start = Date.now(); try { - const output = await (originalExecute as ToolExecuteFunction)(input, opts); + const output = await originalExecute(input, opts); const entry: ToolCallLogEntry = { ts: new Date().toISOString(), wuKey, diff --git a/packages/context/src/ingest/tools/verification-ledger.tool.ts b/packages/context/src/ingest/tools/verification-ledger.tool.ts index af27b58d..e899f8e7 100644 --- a/packages/context/src/ingest/tools/verification-ledger.tool.ts +++ b/packages/context/src/ingest/tools/verification-ledger.tool.ts @@ -1,5 +1,5 @@ -import { tool, type ToolExecuteFunction, type ToolExecutionOptions, type ToolSet } from 'ai'; import { z } from 'zod'; +import { createAgentTool, type AgentToolDefinition, type AgentToolSet } from '../../agent/index.js'; const verificationLedgerInputSchema = z.object({ summary: z.string().min(1).max(2000), @@ -37,31 +37,31 @@ export function createVerificationLedgerState(): VerificationLedgerState { return { entries: [] }; } -export function withVerificationLedger(tools: ToolSet, state: VerificationLedgerState): ToolSet { - const wrapped: ToolSet = {}; +export function withVerificationLedger(tools: AgentToolSet, state: VerificationLedgerState): AgentToolSet { + const wrapped: AgentToolSet = {}; for (const [name, original] of Object.entries(tools)) { - if (!WRITE_TOOL_NAMES.has(name) || typeof original.execute !== 'function') { + if (!WRITE_TOOL_NAMES.has(name)) { wrapped[name] = original; continue; } - const originalExecute = original.execute; - const guardedExecute: ToolExecuteFunction = async ( - input: unknown, - opts: ToolExecutionOptions, - ) => { - if (state.entries.length === 0) { - return verificationRequiredOutput(name); - } - return (originalExecute as ToolExecuteFunction)(input, opts); + const guardedTool: AgentToolDefinition = { + ...original, + execute: async (input, options) => { + if (state.entries.length === 0) { + return verificationRequiredOutput(name); + } + return original.execute(input, options); + }, }; - wrapped[name] = { ...original, execute: guardedExecute }; + wrapped[name] = guardedTool; } wrapped.record_verification_ledger = createRecordVerificationLedgerTool(state); return wrapped; } function createRecordVerificationLedgerTool(state: VerificationLedgerState) { - return tool({ + return createAgentTool({ + name: 'record_verification_ledger', description: 'Record the pre-write verification ledger required by loaded ingest skills. Call this before wiki/SL/fallback writes to state what was verified, which tool calls support it, and what remains intentionally unverified.', inputSchema: verificationLedgerInputSchema, diff --git a/packages/context/src/llm/local-config.test.ts b/packages/context/src/llm/local-config.test.ts index 59ad34b7..b2dc9f27 100644 --- a/packages/context/src/llm/local-config.test.ts +++ b/packages/context/src/llm/local-config.test.ts @@ -22,6 +22,7 @@ describe('local KTX LLM config', () => { }, models: { default: 'env:KTX_MODEL', triage: 'anthropic/claude-haiku-4-5' }, promptCaching: { enabled: false }, + agentRunner: { backend: 'ai-sdk' }, }; expect( @@ -45,6 +46,7 @@ describe('local KTX LLM config', () => { }, models: { default: 'env:KTX_MODEL' }, promptCaching: { enabled: true, vertexFallbackTo5m: true }, + agentRunner: { backend: 'ai-sdk' }, }; expect( @@ -69,6 +71,7 @@ describe('local KTX LLM config', () => { vertex: { location: 'env:MISSING_VERTEX_LOCATION' }, }, models: { default: 'claude-sonnet-4-6' }, + agentRunner: { backend: 'ai-sdk' }, }; expect( @@ -88,6 +91,7 @@ describe('local KTX LLM config', () => { createLocalKtxLlmProviderFromConfig({ provider: { backend: 'none' }, models: {}, + agentRunner: { backend: 'ai-sdk' }, }), ).toBeNull(); }); @@ -101,6 +105,7 @@ describe('local KTX LLM config', () => { anthropic: { api_key: 'env:ANTHROPIC_API_KEY' }, // pragma: allowlist secret }, models: { default: 'claude-sonnet-4-6' }, + agentRunner: { backend: 'ai-sdk' }, }, { env: { ANTHROPIC_API_KEY: 'sk-ant-test' }, createKtxLlmProvider }, // pragma: allowlist secret ); @@ -121,6 +126,7 @@ describe('local KTX LLM config', () => { gateway: { base_url: 'https://gateway.example/v1' }, }, models: { default: 'anthropic/claude-sonnet-4-6' }, + agentRunner: { backend: 'ai-sdk' }, }); expect(provider?.promptCachingConfig()).toMatchObject({ diff --git a/packages/context/src/mcp/local-project-ports.test.ts b/packages/context/src/mcp/local-project-ports.test.ts index 0c000831..03d0c4a1 100644 --- a/packages/context/src/mcp/local-project-ports.test.ts +++ b/packages/context/src/mcp/local-project-ports.test.ts @@ -617,6 +617,7 @@ describe('createLocalProjectMcpContextPorts', () => { project.config.llm = { provider: { backend: 'none' }, models: {}, + agentRunner: { backend: 'ai-sdk' }, }; const sourceDir = join(tempDir, 'source'); @@ -1265,6 +1266,7 @@ describe('createLocalProjectMcpContextPorts', () => { project.config.llm = { provider: { backend: 'none' }, models: {}, + agentRunner: { backend: 'ai-sdk' }, }; const sourceDir = join(project.projectDir, 'upload'); await mkdir(join(sourceDir, 'orders'), { recursive: true }); @@ -1442,6 +1444,7 @@ describe('createLocalProjectMcpContextPorts', () => { project.config.llm = { provider: { backend: 'none' }, models: {}, + agentRunner: { backend: 'ai-sdk' }, }; const agentRunner = new TestAgentRunner(); const ports = createLocalProjectMcpContextPorts(project, { diff --git a/packages/context/src/memory/memory-agent.service.ingest.test.ts b/packages/context/src/memory/memory-agent.service.ingest.test.ts index 2df4140c..c3f84363 100644 --- a/packages/context/src/memory/memory-agent.service.ingest.test.ts +++ b/packages/context/src/memory/memory-agent.service.ingest.test.ts @@ -124,11 +124,11 @@ const buildMocks = (overrides: Partial = {}): BuiltMocks => { slValidator: { validateSingleSource: vi.fn().mockResolvedValue({ errors: [], warnings: [] }) }, toolsetFactory: { createIngestWuToolset: vi.fn().mockReturnValue({ - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), }), createToolset: vi.fn().mockReturnValue({ - toAiSdkTools: vi.fn().mockReturnValue({}), + toAgentTools: vi.fn().mockReturnValue({}), getAllTools: vi.fn().mockReturnValue([]), }), }, diff --git a/packages/context/src/memory/memory-agent.service.ts b/packages/context/src/memory/memory-agent.service.ts index d7e86d3d..5d6afb01 100644 --- a/packages/context/src/memory/memory-agent.service.ts +++ b/packages/context/src/memory/memory-agent.service.ts @@ -1,9 +1,9 @@ import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { tool } from 'ai'; import * as YAML from 'yaml'; import { z } from 'zod'; +import { createAgentTool } from '../agent/index.js'; import { type KtxLogger, noopLogger } from '../core/index.js'; import { revertSourceToPreHead, @@ -126,7 +126,8 @@ export class MemoryAgentService { }; const loadSkillTool = { - load_skill: tool({ + load_skill: createAgentTool({ + name: 'load_skill', description: 'Load a skill to get specialized instructions. Call this when a skill listed in the system prompt matches the current task.', inputSchema: z.object({ @@ -212,7 +213,7 @@ export class MemoryAgentService { modelRole: 'candidateExtraction', systemPrompt, userPrompt: prompt, - toolSet: { ...toolset.toAiSdkTools(toolContext), ...loadSkillTool }, + toolSet: { ...toolset.toAgentTools(toolContext), ...loadSkillTool }, stepBudget, telemetryTags: { operationName: 'memory-agent-ingest', diff --git a/packages/context/src/scan/local-enrichment.test.ts b/packages/context/src/scan/local-enrichment.test.ts index f0ddd448..6a782f17 100644 --- a/packages/context/src/scan/local-enrichment.test.ts +++ b/packages/context/src/scan/local-enrichment.test.ts @@ -820,6 +820,7 @@ describe('local scan enrichment', () => { gateway: {}, }, models: { default: 'provider/language-model' }, + agentRunner: { backend: 'ai-sdk' }, }, { createKtxLlmProvider: createKtxLlmProvider as any,