@ziggs-ai/agent-sdk 0.1.10 → 0.1.12
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/package.json +6 -3
- package/src/AgentHost.ts +347 -28
- package/src/adapters/AnthropicAdapter.ts +238 -0
- package/src/adapters/McpAdapter.ts +391 -15
- package/src/adapters/OpenAIAdapter.ts +20 -8
- package/src/adapters/index.ts +1 -0
- package/src/agent/Agent.ts +13 -2
- package/src/agent/ClaudeSdkAgent.ts +401 -0
- package/src/agent/claudeCognition.ts +139 -0
- package/src/agent/claudeSdkProtocol.ts +27 -0
- package/src/agent/claudeSdkTools.ts +81 -0
- package/src/agent/hostedAgentConnection.ts +41 -0
- package/src/agent/toolManagerToSdkMcp.ts +136 -0
- package/src/defineAgent.ts +22 -7
- package/src/index.ts +14 -5
- package/src/pricing/fleetTierByAgentId.gen.ts +53 -1
- package/src/runtime/AgentMachine.ts +143 -23
- package/src/runtime/PromptBuilder.ts +62 -6
- package/src/runtime/bootstrapCtx.ts +39 -0
- package/src/runtime/buildOutcome.ts +51 -6
- package/src/runtime/defaults.ts +12 -1
- package/src/runtime/deriveRespondsToProposals.ts +35 -0
- package/src/runtime/kickoff.ts +117 -0
- package/src/runtime/runMechanical.ts +106 -0
- package/src/runtime/runTurn.ts +236 -62
- package/src/runtime/validateWorkflow.ts +55 -15
- package/src/server/ConnectionPool.ts +56 -5
- package/src/server/InboxCatchUp.ts +37 -0
- package/src/server/StateTimers.ts +85 -0
- package/src/server/ZiggsEffectHandler.ts +198 -180
- package/src/server/agreements/AgreementService.ts +3 -3
- package/src/server/tasks/TaskService.ts +15 -3
- package/src/server/tools/tier1/protocolRunner.ts +221 -3
- package/src/server/tools/tier1/protocolTools.ts +19 -4
- package/src/types.ts +105 -29
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import type { LlmMessage, LlmResponse, LlmToolCall, LlmToolSchema } from '../types.js';
|
|
3
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
4
|
+
|
|
5
|
+
interface AdapterOptions {
|
|
6
|
+
key?: string;
|
|
7
|
+
model?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Claude 4.7+ models reject sampling params (temperature/top_p/top_k) with a
|
|
11
|
+
// 400 and run without thinking when the `thinking` param is omitted. We omit
|
|
12
|
+
// both on purpose: the SDK's LlmMessage history cannot round-trip thinking
|
|
13
|
+
// blocks through the tool loop, so thinking must stay off.
|
|
14
|
+
const MAX_COMPLETION_TOKENS = 16000;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Anthropic adapter satisfying the same LlmAdapter contract as OpenAIAdapter:
|
|
18
|
+
* OpenAI-wire-shaped LlmMessage[] in, { content, tool_calls, usage } out.
|
|
19
|
+
* Selected by AgentHost when the configured model id starts with "claude".
|
|
20
|
+
*/
|
|
21
|
+
export class AnthropicAdapter {
|
|
22
|
+
private client: Anthropic;
|
|
23
|
+
model: string;
|
|
24
|
+
|
|
25
|
+
constructor({ key, model = 'claude-opus-4-8' }: AdapterOptions) {
|
|
26
|
+
this.client = new Anthropic({ apiKey: key });
|
|
27
|
+
this.model = model;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async chat(
|
|
31
|
+
prompt: string,
|
|
32
|
+
tools: LlmToolSchema[] = [],
|
|
33
|
+
_temperature = 0,
|
|
34
|
+
systemHints: LlmMessage[] = [],
|
|
35
|
+
options: Record<string, unknown> = {},
|
|
36
|
+
): Promise<LlmResponse> {
|
|
37
|
+
const messages: LlmMessage[] = [
|
|
38
|
+
{ role: 'system', content: 'Respond with valid JSON.' },
|
|
39
|
+
...systemHints,
|
|
40
|
+
{ role: 'user', content: prompt },
|
|
41
|
+
];
|
|
42
|
+
return this.chatMessages(messages, tools, options);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async chatMessages(
|
|
46
|
+
messages: LlmMessage[],
|
|
47
|
+
tools: LlmToolSchema[] = [],
|
|
48
|
+
_options: Record<string, unknown> = {},
|
|
49
|
+
): Promise<LlmResponse> {
|
|
50
|
+
const { system, anthropicMessages } = translateMessages(messages);
|
|
51
|
+
const anthropicTools = toAnthropicTools(tools);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const res = await this.client.messages.create({
|
|
55
|
+
model: this.model,
|
|
56
|
+
max_tokens: MAX_COMPLETION_TOKENS,
|
|
57
|
+
...(system ? { system } : {}),
|
|
58
|
+
messages: anthropicMessages,
|
|
59
|
+
...(anthropicTools ? { tools: anthropicTools } : {}),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (res.stop_reason === 'refusal') {
|
|
63
|
+
throw new Error(`LLM refused the request (stop_reason=refusal)`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const content = res.content
|
|
67
|
+
.filter((b): b is Anthropic.TextBlock => b.type === 'text')
|
|
68
|
+
.map(b => b.text)
|
|
69
|
+
.join('')
|
|
70
|
+
.trim() || null;
|
|
71
|
+
const toolCalls: LlmToolCall[] | null = (() => {
|
|
72
|
+
const uses = res.content.filter((b): b is Anthropic.ToolUseBlock => b.type === 'tool_use');
|
|
73
|
+
if (!uses.length) return null;
|
|
74
|
+
return uses.map(u => ({
|
|
75
|
+
id: u.id,
|
|
76
|
+
function: { name: u.name, arguments: JSON.stringify(u.input ?? {}) },
|
|
77
|
+
}));
|
|
78
|
+
})();
|
|
79
|
+
|
|
80
|
+
if (!content && !toolCalls && res.stop_reason === 'max_tokens') {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`LLM returned no usable output — completion token budget exhausted before any content (stop_reason=max_tokens)`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
content: content ?? undefined,
|
|
88
|
+
tool_calls: toolCalls ?? undefined,
|
|
89
|
+
usage: res.usage
|
|
90
|
+
? {
|
|
91
|
+
prompt_tokens: res.usage.input_tokens,
|
|
92
|
+
completion_tokens: res.usage.output_tokens,
|
|
93
|
+
total_tokens: res.usage.input_tokens + res.usage.output_tokens,
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const err = error as Error & { status?: number };
|
|
99
|
+
runtimeLog.error('AnthropicAdapter', `LLM API call failed: ${err.message}`, {
|
|
100
|
+
model: this.model,
|
|
101
|
+
error: err.message,
|
|
102
|
+
status: err.status ?? 'unknown',
|
|
103
|
+
});
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async chatMessagesStream(
|
|
109
|
+
messages: LlmMessage[],
|
|
110
|
+
onChunk: (text: string) => void,
|
|
111
|
+
): Promise<{ text: string; usage: { prompt_tokens: number; completion_tokens: number; total_tokens: number } | undefined }> {
|
|
112
|
+
const { system, anthropicMessages } = translateMessages(messages);
|
|
113
|
+
const stream = this.client.messages.stream({
|
|
114
|
+
model: this.model,
|
|
115
|
+
max_tokens: MAX_COMPLETION_TOKENS,
|
|
116
|
+
...(system ? { system } : {}),
|
|
117
|
+
messages: anthropicMessages,
|
|
118
|
+
});
|
|
119
|
+
let text = '';
|
|
120
|
+
stream.on('text', (delta) => {
|
|
121
|
+
text += delta;
|
|
122
|
+
onChunk(delta);
|
|
123
|
+
});
|
|
124
|
+
const final = await stream.finalMessage();
|
|
125
|
+
const u = final.usage;
|
|
126
|
+
// Fold cache read/creation into prompt tokens so the streamed reply is not
|
|
127
|
+
// undercounted (cache traffic was the bulk of the ZIG-759 drain).
|
|
128
|
+
const usage = u
|
|
129
|
+
? {
|
|
130
|
+
prompt_tokens: (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0),
|
|
131
|
+
completion_tokens: u.output_tokens ?? 0,
|
|
132
|
+
total_tokens:
|
|
133
|
+
(u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0) + (u.output_tokens ?? 0),
|
|
134
|
+
}
|
|
135
|
+
: undefined;
|
|
136
|
+
return { text, usage };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Convert the runtime's tool list into Anthropic's `tools` param.
|
|
142
|
+
*
|
|
143
|
+
* The declared `LlmToolSchema` type (`{ type, function }`) is a lie at runtime:
|
|
144
|
+
* `runTurn` launders `ToolDefinition[]` (shape `{ schema: { function } }`)
|
|
145
|
+
* through an `as unknown as LlmToolSchema[]` cast before it reaches any adapter.
|
|
146
|
+
* OpenAIAdapter already reads `t.schema.function || t.schema`; this must do the
|
|
147
|
+
* same, or every claude-backed agent whose state exposes ≥1 tool crashes on
|
|
148
|
+
* `t.function` being undefined ("Cannot read properties of undefined (reading
|
|
149
|
+
* 'name')"). Both shapes are accepted so raw wire-shaped tools also work.
|
|
150
|
+
*/
|
|
151
|
+
export function toAnthropicTools(tools: LlmToolSchema[] = []): Anthropic.Tool[] | undefined {
|
|
152
|
+
if (!tools.length) return undefined;
|
|
153
|
+
return tools.map(t => {
|
|
154
|
+
const td = t as unknown as {
|
|
155
|
+
schema?: { function?: Record<string, unknown> };
|
|
156
|
+
function?: Record<string, unknown>;
|
|
157
|
+
};
|
|
158
|
+
const fn = (td.schema?.function ?? td.function ?? {}) as Record<string, unknown>;
|
|
159
|
+
return {
|
|
160
|
+
name: fn['name'] as string,
|
|
161
|
+
description: (fn['description'] as string) ?? '',
|
|
162
|
+
input_schema: (fn['parameters'] ?? { type: 'object' }) as Anthropic.Tool.InputSchema,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* OpenAI-wire-shaped LlmMessage[] → Anthropic { system, messages }.
|
|
169
|
+
* - system messages concatenate into the top-level system param
|
|
170
|
+
* - assistant tool_calls become tool_use blocks (arguments JSON-parsed)
|
|
171
|
+
* - role:"tool" results become user-turn tool_result blocks
|
|
172
|
+
* Consecutive same-role turns are merged (Anthropic requires alternation to
|
|
173
|
+
* start with user; the API tolerates consecutive same-role, but tool_result
|
|
174
|
+
* blocks must open the user turn that answers a tool_use turn).
|
|
175
|
+
*/
|
|
176
|
+
function translateMessages(messages: LlmMessage[]): {
|
|
177
|
+
system: string | undefined;
|
|
178
|
+
anthropicMessages: Anthropic.MessageParam[];
|
|
179
|
+
} {
|
|
180
|
+
const systemParts: string[] = [];
|
|
181
|
+
const out: Anthropic.MessageParam[] = [];
|
|
182
|
+
|
|
183
|
+
for (const m of messages) {
|
|
184
|
+
if (m.role === 'system') {
|
|
185
|
+
if (m.content) systemParts.push(m.content);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (m.role === 'tool') {
|
|
189
|
+
const block: Anthropic.ToolResultBlockParam = {
|
|
190
|
+
type: 'tool_result',
|
|
191
|
+
tool_use_id: m.tool_call_id ?? '',
|
|
192
|
+
content: m.content ?? '',
|
|
193
|
+
};
|
|
194
|
+
const last = out[out.length - 1];
|
|
195
|
+
if (last && last.role === 'user' && Array.isArray(last.content)) {
|
|
196
|
+
(last.content as Anthropic.ContentBlockParam[]).push(block);
|
|
197
|
+
} else {
|
|
198
|
+
out.push({ role: 'user', content: [block] });
|
|
199
|
+
}
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (m.role === 'assistant' && m.tool_calls?.length) {
|
|
203
|
+
const blocks: Anthropic.ContentBlockParam[] = [];
|
|
204
|
+
if (m.content) blocks.push({ type: 'text', text: m.content });
|
|
205
|
+
for (const tc of m.tool_calls) {
|
|
206
|
+
blocks.push({
|
|
207
|
+
type: 'tool_use',
|
|
208
|
+
id: tc.id,
|
|
209
|
+
name: tc.function.name,
|
|
210
|
+
input: safeParseJson(tc.function.arguments),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
out.push({ role: 'assistant', content: blocks });
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
out.push({ role: m.role, content: m.content ?? '' });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!out.length || out[0].role !== 'user') {
|
|
220
|
+
out.unshift({ role: 'user', content: '(session start)' });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
system: systemParts.length ? systemParts.join('\n\n') : undefined,
|
|
225
|
+
anthropicMessages: out,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function safeParseJson(s: string): Record<string, unknown> {
|
|
230
|
+
try {
|
|
231
|
+
const v = JSON.parse(s);
|
|
232
|
+
return typeof v === 'object' && v !== null ? v : {};
|
|
233
|
+
} catch {
|
|
234
|
+
// Malformed arguments string from a prior turn's history: replaying it as
|
|
235
|
+
// an empty input beats crashing the whole turn on someone else's output.
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
}
|