@ziggs-ai/agent-sdk 0.1.3 → 0.1.4
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 +1 -1
- package/package.json +9 -4
- package/src/AgentHost.ts +342 -0
- package/src/adapters/OpenAIAdapter.ts +125 -0
- package/src/agent/Agent.ts +98 -0
- package/src/cognition/validateContext.ts +95 -0
- package/src/context/applyEffects.ts +80 -0
- package/src/context/batch.ts +17 -0
- package/src/context/classifyEnvelope.ts +38 -0
- package/src/context/routingLabels.ts +46 -0
- package/src/defineAgent.ts +62 -0
- package/src/formatters/AgreementFormatter.ts +111 -0
- package/src/formatters/HistoryFormatter.ts +166 -0
- package/src/formatters/index.ts +2 -0
- package/src/index.ts +86 -0
- package/src/ingress/normalizeIncoming.ts +119 -0
- package/src/memory/MemoryStore.ts +104 -0
- package/src/runtime/AgentMachine.ts +298 -0
- package/src/runtime/PromptBuilder.ts +461 -0
- package/src/runtime/buildOutcome.ts +488 -0
- package/src/runtime/defaults.ts +72 -0
- package/src/runtime/runTurn.ts +637 -0
- package/src/runtime/validateWorkflow.ts +165 -0
- package/src/server/ConnectionPool.ts +155 -0
- package/src/server/EventQueue.ts +119 -0
- package/src/server/OutboxBuffer.ts +90 -0
- package/src/server/ZiggsEffectHandler.ts +335 -0
- package/src/server/agreements/AgreementService.ts +111 -0
- package/src/server/createHealthServer.ts +8 -0
- package/src/server/proactive/ProactiveTrigger.ts +83 -0
- package/src/server/runLauncher.ts +131 -0
- package/src/server/tasks/TaskService.ts +111 -0
- package/src/server/tasks/index.ts +4 -0
- package/src/server/tasks/paymentTools.ts +156 -0
- package/src/server/tasks/protocolRunner.ts +101 -0
- package/src/server/tasks/protocolTools.ts +96 -0
- package/src/server/ziggspay/ZiggsPayClient.ts +193 -0
- package/src/shared/ids.ts +3 -0
- package/src/shared/runtimeLog.ts +72 -0
- package/src/shared/types.ts +31 -0
- package/src/tasks/protocolRegistry.ts +25 -0
- package/src/tasks/taskCore.ts +139 -0
- package/src/tools/ToolManager.ts +95 -0
- package/src/tools/{ToolProvider.js → ToolProvider.ts} +5 -15
- package/src/tools/defineTool.ts +90 -0
- package/src/tools/index.ts +5 -0
- package/src/types.ts +368 -0
- package/src/utils/jsonExtractor.ts +100 -0
- package/src/ConnectionPool.js +0 -133
- package/src/adapters/OpenAIAdapter.js +0 -73
- package/src/agent/Agent.js +0 -121
- package/src/agent/EventQueue.js +0 -68
- package/src/agent/OutboxBuffer.js +0 -62
- package/src/cognition/PromptBuilder.js +0 -312
- package/src/cognition/resolveActionTool.js +0 -12
- package/src/cognition/runTurn.js +0 -578
- package/src/context/applyEffects.js +0 -133
- package/src/context/batch.js +0 -25
- package/src/context/classifyEnvelope.js +0 -82
- package/src/context/routingLabels.js +0 -54
- package/src/createHealthServer.js +0 -28
- package/src/formatters/HistoryFormatter.js +0 -257
- package/src/formatters/TaskFormatter.js +0 -180
- package/src/formatters/index.js +0 -9
- package/src/index.js +0 -76
- package/src/ingress/normalizeIncoming.js +0 -70
- package/src/runLauncher.js +0 -159
- package/src/shared/ids.js +0 -7
- package/src/shared/types.js +0 -86
- package/src/tasks/TaskService.js +0 -247
- package/src/tasks/index.js +0 -9
- package/src/tasks/taskCore.js +0 -229
- package/src/tasks/taskProtocolRegistry.js +0 -22
- package/src/tasks/taskProtocolRunner.js +0 -107
- package/src/tasks/taskProtocolTools.js +0 -87
- package/src/tools/ToolManager.js +0 -79
- package/src/tools/defineTool.js +0 -82
- package/src/tools/index.js +0 -11
- package/src/utils/jsonExtractor.js +0 -139
- package/src/workflow/AgentMachine.js +0 -250
- package/src/workflow/WorkflowRuntime.js +0 -63
- package/src/workflow/dsl.js +0 -287
- package/src/workflow/motifs.js +0 -435
- package/src/ziggs/runtime.js +0 -192
- /package/src/adapters/{index.js → index.ts} +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide log levels for agentplus runtimes. Default is `info` so FSM /
|
|
3
|
+
* socket chatter stays quiet unless operators opt in.
|
|
4
|
+
*
|
|
5
|
+
* Env (first match wins for the debug shortcut):
|
|
6
|
+
* - `DEBUG_AGENTPLUS=1` or `AGENTPLUS_DEBUG=1` → threshold `debug`
|
|
7
|
+
* - `LOG_LEVEL` or `AGENTPLUS_LOG_LEVEL` → `debug` | `info` | `warn` | `error` | `silent`
|
|
8
|
+
* (`silent` keeps only `error` output)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type RuntimeLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
12
|
+
|
|
13
|
+
const SEVERITY: Record<RuntimeLogLevel, number> = {
|
|
14
|
+
debug: 0,
|
|
15
|
+
info: 1,
|
|
16
|
+
warn: 2,
|
|
17
|
+
error: 3,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function parseThreshold(): number {
|
|
21
|
+
if (process.env.DEBUG_AGENTPLUS === '1' || process.env.AGENTPLUS_DEBUG === '1') {
|
|
22
|
+
return SEVERITY.debug;
|
|
23
|
+
}
|
|
24
|
+
const raw = (
|
|
25
|
+
process.env.LOG_LEVEL ||
|
|
26
|
+
process.env.AGENTPLUS_LOG_LEVEL ||
|
|
27
|
+
'info'
|
|
28
|
+
).toLowerCase();
|
|
29
|
+
if (raw === 'silent' || raw === 'none') return SEVERITY.error;
|
|
30
|
+
if (raw === 'debug' || raw === 'trace') return SEVERITY.debug;
|
|
31
|
+
if (raw === 'warn') return SEVERITY.warn;
|
|
32
|
+
if (raw === 'error') return SEVERITY.error;
|
|
33
|
+
return SEVERITY.info;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let cachedThreshold: number | null = null;
|
|
37
|
+
|
|
38
|
+
function threshold(): number {
|
|
39
|
+
if (cachedThreshold === null) cachedThreshold = parseThreshold();
|
|
40
|
+
return cachedThreshold;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Test hook: re-read env after `process.env` mutations. */
|
|
44
|
+
export function resetRuntimeLogLevelCache(): void {
|
|
45
|
+
cachedThreshold = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shouldEmit(level: RuntimeLogLevel): boolean {
|
|
49
|
+
return SEVERITY[level] >= threshold();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fmt(scope: string, msg: string): string {
|
|
53
|
+
return `[${scope}] ${msg}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const runtimeLog = {
|
|
57
|
+
debug(scope: string, msg: string, ...rest: unknown[]): void {
|
|
58
|
+
if (!shouldEmit('debug')) return;
|
|
59
|
+
console.log(fmt(scope, msg), ...rest);
|
|
60
|
+
},
|
|
61
|
+
info(scope: string, msg: string, ...rest: unknown[]): void {
|
|
62
|
+
if (!shouldEmit('info')) return;
|
|
63
|
+
console.log(fmt(scope, msg), ...rest);
|
|
64
|
+
},
|
|
65
|
+
warn(scope: string, msg: string, ...rest: unknown[]): void {
|
|
66
|
+
if (!shouldEmit('warn')) return;
|
|
67
|
+
console.warn(fmt(scope, msg), ...rest);
|
|
68
|
+
},
|
|
69
|
+
error(scope: string, msg: string, ...rest: unknown[]): void {
|
|
70
|
+
console.error(fmt(scope, msg), ...rest);
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Inlined from `@ziggs-ai/api-client` so the pure SDK doesn't depend on the
|
|
2
|
+
// Ziggs HTTP client. Both client and server agree on these string constants
|
|
3
|
+
// because the protocol is the same — they're just two views of one schema.
|
|
4
|
+
export const EntryTypes = {
|
|
5
|
+
MESSAGE: 'message',
|
|
6
|
+
NOTIFICATION: 'notification',
|
|
7
|
+
ARTIFACT: 'artifact',
|
|
8
|
+
TASK_HISTORY: 'task_history',
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type EntryType = (typeof EntryTypes)[keyof typeof EntryTypes];
|
|
12
|
+
|
|
13
|
+
export const ContentTypes = {
|
|
14
|
+
TEXT: 'text',
|
|
15
|
+
OPERATION: 'operation',
|
|
16
|
+
CONTEXT: 'context',
|
|
17
|
+
RESULT: 'result',
|
|
18
|
+
TASK_UPDATE: 'task_update',
|
|
19
|
+
THOUGHT: 'thought',
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
export type ContentType = (typeof ContentTypes)[keyof typeof ContentTypes];
|
|
23
|
+
|
|
24
|
+
export { TaskState, TERMINAL_STATES, OPEN_AGREEMENT_TARGET } from '../tasks/taskCore.js';
|
|
25
|
+
|
|
26
|
+
export const ContextProperties = {
|
|
27
|
+
HISTORY: 'history',
|
|
28
|
+
AGREEMENTS: 'agreements',
|
|
29
|
+
AGENTS: 'agents',
|
|
30
|
+
USERS: 'users',
|
|
31
|
+
} as const;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const PROTOCOL_TOOL_TO_OPERATION = Object.freeze({
|
|
2
|
+
agreement_propose: 'agreement-propose',
|
|
3
|
+
agreement_subcontract: 'agreement-subcontract',
|
|
4
|
+
agreement_respond: 'agreement-respond',
|
|
5
|
+
agreement_counter_proposal: 'agreement-counter-proposal',
|
|
6
|
+
agreement_check_proposal: 'agreement-check-proposal',
|
|
7
|
+
agreement_revoke: 'agreement-revoke',
|
|
8
|
+
task_spawn: 'task-spawn',
|
|
9
|
+
task_update: 'task-update',
|
|
10
|
+
task_update_plan_step: 'task-update-plan-step',
|
|
11
|
+
} as const);
|
|
12
|
+
|
|
13
|
+
export type ProtocolToolName = keyof typeof PROTOCOL_TOOL_TO_OPERATION;
|
|
14
|
+
export type ProtocolOperation = (typeof PROTOCOL_TOOL_TO_OPERATION)[ProtocolToolName];
|
|
15
|
+
|
|
16
|
+
export const PROTOCOL_TOOL_NAMES = Object.freeze(Object.keys(PROTOCOL_TOOL_TO_OPERATION)) as readonly ProtocolToolName[];
|
|
17
|
+
|
|
18
|
+
export function mapProtocolToolToOperation(toolName: string): ProtocolOperation | null {
|
|
19
|
+
if (!toolName || typeof toolName !== 'string') return null;
|
|
20
|
+
return (PROTOCOL_TOOL_TO_OPERATION as Record<string, ProtocolOperation>)[toolName] ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isProtocolToolName(toolName: string): toolName is ProtocolToolName {
|
|
24
|
+
return mapProtocolToolToOperation(toolName) != null;
|
|
25
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Task definitions — states, state machine, shapes, and helpers.
|
|
3
|
+
* ⚠️ SYNC: Keep in sync with backend/src/tasks/task-core.ts
|
|
4
|
+
* and backend/src/agreements/agreement.schema.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const TaskState = {
|
|
8
|
+
PROPOSAL: 'proposal',
|
|
9
|
+
ACTIVE: 'active',
|
|
10
|
+
COMPLETED: 'completed',
|
|
11
|
+
FAILED: 'failed',
|
|
12
|
+
CANCELLED: 'cancelled',
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type TaskStateValue = (typeof TaskState)[keyof typeof TaskState];
|
|
16
|
+
|
|
17
|
+
export const TASK_STATES: TaskStateValue[] = ['proposal', 'active', 'completed', 'failed', 'cancelled'];
|
|
18
|
+
export const TERMINAL_STATES: TaskStateValue[] = ['completed', 'failed', 'cancelled'];
|
|
19
|
+
|
|
20
|
+
export const VALID_TRANSITIONS: Partial<Record<TaskStateValue, TaskStateValue[]>> = {
|
|
21
|
+
proposal: ['active', 'cancelled'],
|
|
22
|
+
active: ['completed', 'failed', 'cancelled'],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function isTerminal(state: string): boolean {
|
|
26
|
+
return TERMINAL_STATES.includes(state as TaskStateValue);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function canTransitionTo(from: string, to: string): boolean {
|
|
30
|
+
if (isTerminal(from)) return false;
|
|
31
|
+
const allowed = VALID_TRANSITIONS[from as TaskStateValue];
|
|
32
|
+
if (!allowed) return false;
|
|
33
|
+
return allowed.includes(to as TaskStateValue);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function normalizeState(state: string): string {
|
|
37
|
+
if (state === 'done') return TaskState.COMPLETED;
|
|
38
|
+
if (state === 'error') return TaskState.FAILED;
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isValidState(state: string): boolean {
|
|
43
|
+
return TASK_STATES.includes(state as TaskStateValue);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Shapes ────────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
export interface PlanStep {
|
|
49
|
+
stepId: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
order?: number;
|
|
52
|
+
status?: 'pending' | 'in_progress' | 'completed' | 'skipped';
|
|
53
|
+
result?: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TaskHistoryEntry {
|
|
57
|
+
changeType: string;
|
|
58
|
+
[key: string]: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AgreementParties {
|
|
62
|
+
creatorId?: string | null;
|
|
63
|
+
providerId?: string | null;
|
|
64
|
+
payerId?: string | null;
|
|
65
|
+
proposedToId?: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface AgreementTerms {
|
|
69
|
+
price?: number | null;
|
|
70
|
+
lifecycle?: string | null;
|
|
71
|
+
expiresAt?: string | null;
|
|
72
|
+
maxExecutions?: number | null;
|
|
73
|
+
description?: string | null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AgreementMoney {
|
|
77
|
+
price?: number | null;
|
|
78
|
+
paymentStatus?: string | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface AgreementProposal {
|
|
82
|
+
status: 'pending' | 'approved' | 'rejected' | null;
|
|
83
|
+
respondedBy?: string | null;
|
|
84
|
+
proposedAt?: string | null;
|
|
85
|
+
respondedAt?: string | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const HistoryChangeType = {
|
|
89
|
+
CREATED: 'created',
|
|
90
|
+
STATE_CHANGED: 'state_changed',
|
|
91
|
+
PROCESSING_CHANGED: 'processing_changed',
|
|
92
|
+
PLAN_STEP_CHANGED: 'plan_step_changed',
|
|
93
|
+
PLAN_RESTRUCTURED: 'plan_restructured',
|
|
94
|
+
PLAN_ACKNOWLEDGED: 'plan_acknowledged',
|
|
95
|
+
} as const;
|
|
96
|
+
|
|
97
|
+
export type HistoryChangeTypeValue = (typeof HistoryChangeType)[keyof typeof HistoryChangeType];
|
|
98
|
+
|
|
99
|
+
export interface Agreement {
|
|
100
|
+
agreementId: string;
|
|
101
|
+
taskId?: string | null;
|
|
102
|
+
rootAgreementId?: string | null;
|
|
103
|
+
parentAgreementId?: string | null;
|
|
104
|
+
parties: AgreementParties;
|
|
105
|
+
terms: AgreementTerms;
|
|
106
|
+
money: AgreementMoney;
|
|
107
|
+
proposal: AgreementProposal;
|
|
108
|
+
status: 'draft' | 'open' | 'active' | 'fulfilled' | 'cancelled' | 'refunded';
|
|
109
|
+
createdAt: string;
|
|
110
|
+
updatedAt: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface Task {
|
|
114
|
+
taskId: string;
|
|
115
|
+
description: string;
|
|
116
|
+
agreementId: string;
|
|
117
|
+
agreement?: Agreement | null;
|
|
118
|
+
state: TaskStateValue;
|
|
119
|
+
processing: boolean;
|
|
120
|
+
deleted: boolean;
|
|
121
|
+
result?: unknown;
|
|
122
|
+
errorMessage?: string | null;
|
|
123
|
+
createdAt: string;
|
|
124
|
+
updatedAt: string;
|
|
125
|
+
parentTaskId?: string | null;
|
|
126
|
+
rootTaskId?: string | null;
|
|
127
|
+
plan?: { steps?: PlanStep[] };
|
|
128
|
+
history?: TaskHistoryEntry[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface TaskWithFlags extends Task {
|
|
132
|
+
creatorIsYou?: boolean;
|
|
133
|
+
providerIsYou?: boolean;
|
|
134
|
+
payerIsYou?: boolean;
|
|
135
|
+
proposedToIsYou?: boolean;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** ⚠️ SYNC: Keep in sync with backend and api-client */
|
|
139
|
+
export const OPEN_AGREEMENT_TARGET = 'everyone' as const;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import AjvModule from 'ajv';
|
|
2
|
+
import { ToolProvider } from './ToolProvider.js';
|
|
3
|
+
import type { ToolDefinition } from './defineTool.js';
|
|
4
|
+
|
|
5
|
+
// Ajv ships as CJS; handle both ESM default-import shapes
|
|
6
|
+
type ValidateFn = ((data: unknown) => boolean) & { errors?: unknown[] };
|
|
7
|
+
type AjvInstance = { compile: (schema: object) => ValidateFn; errorsText: (errors: unknown) => string };
|
|
8
|
+
const AjvClass = (AjvModule as unknown as { default?: unknown }).default ?? AjvModule;
|
|
9
|
+
const ajv = new (AjvClass as new (opts: object) => AjvInstance)({ allErrors: true, strict: false });
|
|
10
|
+
|
|
11
|
+
interface ToolError extends Error {
|
|
12
|
+
type: string;
|
|
13
|
+
cause?: unknown;
|
|
14
|
+
data?: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function makeError(message: string, type: string, extra?: Partial<ToolError>): ToolError {
|
|
18
|
+
const err = new Error(message) as ToolError;
|
|
19
|
+
err.type = type;
|
|
20
|
+
if (extra) Object.assign(err, extra);
|
|
21
|
+
return err;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stripNullish(args: Record<string, unknown>): Record<string, unknown> {
|
|
25
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) return args;
|
|
26
|
+
const out: Record<string, unknown> = {};
|
|
27
|
+
for (const [k, v] of Object.entries(args)) {
|
|
28
|
+
if (v !== null && v !== undefined) out[k] = v;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ToolManager extends ToolProvider {
|
|
34
|
+
private tools: Map<string, ToolDefinition>;
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
super();
|
|
38
|
+
this.tools = new Map();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
register(tool: ToolDefinition): string {
|
|
42
|
+
if (!tool?.schema) throw makeError('Tool must have a schema', 'invalid_tool');
|
|
43
|
+
if (typeof tool.handler !== 'function') throw makeError('Tool must have a handler function', 'invalid_tool');
|
|
44
|
+
const name = (tool.schema.function?.['name'] || (tool.schema as unknown as Record<string, unknown>)['name']) as string;
|
|
45
|
+
if (!name) throw makeError('Tool schema must have a name', 'invalid_tool');
|
|
46
|
+
this.tools.set(name, tool);
|
|
47
|
+
return name;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
registerAll(tools: ToolDefinition[] = []): void {
|
|
51
|
+
for (const tool of tools) this.register(tool);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async getAvailableTools(): Promise<ToolDefinition[]> {
|
|
55
|
+
return Array.from(this.tools.values());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getTool(name: string): ToolDefinition | null {
|
|
59
|
+
const normalized = this._normalizeName(name) as string;
|
|
60
|
+
return this.tools.get(normalized) ?? null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async executeTool(
|
|
64
|
+
name: string,
|
|
65
|
+
args: Record<string, unknown> = {},
|
|
66
|
+
context: Record<string, unknown> = {},
|
|
67
|
+
): Promise<unknown> {
|
|
68
|
+
const normalized = this._normalizeName(name) as string;
|
|
69
|
+
const tool = this.tools.get(normalized);
|
|
70
|
+
if (!tool) throw makeError(`Tool not found: ${name}`, 'tool_not_found');
|
|
71
|
+
|
|
72
|
+
const cleanedArgs = stripNullish(args);
|
|
73
|
+
this._validateArgs(tool, cleanedArgs);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
return await tool.handler(cleanedArgs, context);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw makeError(`Tool "${name}" failed: ${(error as Error).message}`, 'tool_error', { cause: error });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private _validateArgs(tool: ToolDefinition, args: Record<string, unknown>): void {
|
|
83
|
+
const parameters = tool.schema.function?.['parameters'] || (tool.schema as unknown as Record<string, unknown>)['parameters'];
|
|
84
|
+
if (!parameters) return;
|
|
85
|
+
const validate = ajv.compile(parameters as object);
|
|
86
|
+
if (!validate(args)) {
|
|
87
|
+
const name = tool.schema.function?.['name'];
|
|
88
|
+
throw makeError(
|
|
89
|
+
`Invalid args for "${name}": ${ajv.errorsText(validate.errors)}`,
|
|
90
|
+
'validation_error',
|
|
91
|
+
{ data: { errors: validate.errors } },
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for tool providers.
|
|
3
|
-
* ToolManager and any custom providers extend this.
|
|
4
|
-
*/
|
|
5
1
|
export class ToolProvider {
|
|
6
|
-
async getAvailableTools() {
|
|
2
|
+
async getAvailableTools(): Promise<unknown[]> {
|
|
7
3
|
throw new Error('getAvailableTools must be implemented');
|
|
8
4
|
}
|
|
9
|
-
|
|
10
|
-
async executeTool(name, args = {}, context = {}) {
|
|
5
|
+
async executeTool(_name: string, _args?: Record<string, unknown>, _context?: Record<string, unknown>): Promise<unknown> {
|
|
11
6
|
throw new Error('executeTool must be implemented');
|
|
12
7
|
}
|
|
13
|
-
|
|
14
|
-
getTool(name) {
|
|
8
|
+
getTool(_name: string): unknown | null {
|
|
15
9
|
throw new Error('getTool must be implemented');
|
|
16
10
|
}
|
|
17
|
-
|
|
18
|
-
_normalizeName(name) {
|
|
11
|
+
_normalizeName(name: unknown): unknown {
|
|
19
12
|
if (!name || typeof name !== 'string') return name;
|
|
20
|
-
|
|
21
13
|
const prefixes = ['functions.', 'tools.', 'function.', 'tool.'];
|
|
22
14
|
for (const prefix of prefixes) {
|
|
23
|
-
if (name.startsWith(prefix))
|
|
24
|
-
return name.substring(prefix.length);
|
|
25
|
-
}
|
|
15
|
+
if (name.startsWith(prefix)) return name.substring(prefix.length);
|
|
26
16
|
}
|
|
27
17
|
return name;
|
|
28
18
|
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const PRIMITIVES = new Set(['string', 'number', 'boolean', 'integer']);
|
|
2
|
+
|
|
3
|
+
type FieldDef = string | unknown[] | Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
function convertField(value: FieldDef): { schema: Record<string, unknown>; required: boolean } {
|
|
6
|
+
if (typeof value === 'string') {
|
|
7
|
+
if (!PRIMITIVES.has(value)) throw new Error(`Unknown type shorthand: "${value}"`);
|
|
8
|
+
return { schema: { type: value }, required: false };
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value)) {
|
|
11
|
+
if (value.length !== 1) throw new Error('Array shorthand must have exactly one element describing the item type');
|
|
12
|
+
const inner = value[0] as FieldDef;
|
|
13
|
+
if (typeof inner === 'string') return { schema: { type: 'array', items: { type: inner } }, required: false };
|
|
14
|
+
return { schema: { type: 'array', items: convertObject(inner as Record<string, unknown>) }, required: false };
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'object' && value !== null) {
|
|
17
|
+
const v = value as Record<string, unknown>;
|
|
18
|
+
if (v['type'] && PRIMITIVES.has(v['type'] as string)) {
|
|
19
|
+
const { required: isReq, ...rest } = v;
|
|
20
|
+
return { schema: rest, required: !!isReq };
|
|
21
|
+
}
|
|
22
|
+
if (v['type'] === 'array' || v['type'] === 'object') {
|
|
23
|
+
const { required: isReq, ...rest } = v;
|
|
24
|
+
return { schema: rest, required: !!isReq };
|
|
25
|
+
}
|
|
26
|
+
return { schema: convertObject(v), required: false };
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`Cannot convert field value: ${JSON.stringify(value)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function convertObject(fields: Record<string, unknown>): Record<string, unknown> {
|
|
32
|
+
const properties: Record<string, unknown> = {};
|
|
33
|
+
const required: string[] = [];
|
|
34
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
35
|
+
if (key === 'required') continue;
|
|
36
|
+
const { schema, required: isReq } = convertField(value as FieldDef);
|
|
37
|
+
properties[key] = schema;
|
|
38
|
+
if (isReq) required.push(key);
|
|
39
|
+
}
|
|
40
|
+
const result: Record<string, unknown> = { type: 'object', properties };
|
|
41
|
+
if (required.length > 0) result['required'] = required;
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ToolDefinition {
|
|
46
|
+
schema: { type: 'function'; function: Record<string, unknown> };
|
|
47
|
+
handler: (args: Record<string, unknown>, context: Record<string, unknown>) => Promise<unknown>;
|
|
48
|
+
echoUserSummaryOnSuccess?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* When true, the runtime will skip a second invocation of this tool in
|
|
51
|
+
* the same assistant message. Use for "create an agreement"-style tools
|
|
52
|
+
* where the model is prone to fire two near-identical create calls.
|
|
53
|
+
*/
|
|
54
|
+
isAgreementCreation?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* When true, the runtime will execute this tool even when no state
|
|
57
|
+
* action is bound to it. Use for discovery tools (e.g. agent search,
|
|
58
|
+
* task board) that should always be callable.
|
|
59
|
+
*/
|
|
60
|
+
isGenericFallback?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface DefineToolOptions {
|
|
64
|
+
description?: string;
|
|
65
|
+
echoUserSummaryOnSuccess?: boolean;
|
|
66
|
+
isAgreementCreation?: boolean;
|
|
67
|
+
isGenericFallback?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function defineTool(
|
|
71
|
+
name: string,
|
|
72
|
+
params: Record<string, unknown>,
|
|
73
|
+
handler: (args: Record<string, unknown>, context: Record<string, unknown>) => Promise<unknown>,
|
|
74
|
+
options: DefineToolOptions = {},
|
|
75
|
+
): ToolDefinition {
|
|
76
|
+
if (!name || typeof name !== 'string') throw new Error('defineTool: name is required');
|
|
77
|
+
if (!handler || typeof handler !== 'function') throw new Error('defineTool: handler function is required');
|
|
78
|
+
|
|
79
|
+
const parameters = convertObject(params || {});
|
|
80
|
+
const fn: Record<string, unknown> = { name, parameters };
|
|
81
|
+
if (options.description) fn['description'] = options.description;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
schema: { type: 'function', function: fn },
|
|
85
|
+
handler,
|
|
86
|
+
...(options.echoUserSummaryOnSuccess ? { echoUserSummaryOnSuccess: true } : {}),
|
|
87
|
+
...(options.isAgreementCreation ? { isAgreementCreation: true } : {}),
|
|
88
|
+
...(options.isGenericFallback ? { isGenericFallback: true } : {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { ToolManager } from './ToolManager.js';
|
|
2
|
+
export { ToolProvider } from './ToolProvider.js';
|
|
3
|
+
export { defineTool } from './defineTool.js';
|
|
4
|
+
// Pure protocol-tool name registry (no handlers, no services).
|
|
5
|
+
export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from '../tasks/protocolRegistry.js';
|