@niscorp/signal 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,1847 @@
1
+ 'use strict';
2
+
3
+ var zod = require('zod');
4
+
5
+ // src/signal.ts
6
+
7
+ // src/utils/estimate-usage.ts
8
+ var CHARS_PER_TOKEN = 4;
9
+ var PER_MESSAGE = 4;
10
+ var lengthOf = (content) => typeof content === "string" ? content.length : JSON.stringify(content ?? "").length;
11
+ var estimateUsage = (messages, content, toolCalls = []) => {
12
+ let inputTokens = 0;
13
+ for (const message of messages) inputTokens += PER_MESSAGE + Math.ceil(lengthOf(message.content) / CHARS_PER_TOKEN);
14
+ const emitted = content + toolCalls.map((call) => `${call.name}${JSON.stringify(call.args)}`).join("");
15
+ const outputTokens = Math.ceil(emitted.length / CHARS_PER_TOKEN);
16
+ return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, reported: false };
17
+ };
18
+
19
+ // src/errors.ts
20
+ var ErrorCode = {
21
+ PROVIDER_NOT_FOUND: "E_PROVIDER_NOT_FOUND",
22
+ MISSING_API_KEY: "E_MISSING_API_KEY",
23
+ MISSING_MODEL: "E_MISSING_MODEL",
24
+ MISSING_SDK: "E_MISSING_SDK",
25
+ VALIDATION_FAILED: "E_VALIDATION_FAILED",
26
+ MAX_RETRIES: "E_MAX_RETRIES",
27
+ MAX_ITERATIONS: "E_MAX_ITERATIONS",
28
+ PROVIDER_ERROR: "E_PROVIDER_ERROR",
29
+ TOOL_NOT_FOUND: "E_TOOL_NOT_FOUND",
30
+ TOOL_EXECUTION: "E_TOOL_EXECUTION",
31
+ TOOL_VALIDATION: "E_TOOL_VALIDATION",
32
+ VERB_NOT_SUPPORTED: "E_VERB_NOT_SUPPORTED"
33
+ };
34
+ var SignalError = class extends Error {
35
+ code;
36
+ context;
37
+ constructor(message, code, context) {
38
+ super(message);
39
+ this.name = "SignalError";
40
+ this.code = code;
41
+ this.context = context;
42
+ }
43
+ };
44
+
45
+ // src/registry.ts
46
+ var providerRegistry = {
47
+ groq: {
48
+ id: "groq",
49
+ baseUrl: "https://api.groq.com/openai/v1",
50
+ envKey: "GROQ_API_KEY",
51
+ defaultModel: "qwen/qwen3.8-27b",
52
+ endpoint: {
53
+ nativeTools: true,
54
+ nativeJsonMode: true,
55
+ // Groq validates tool args server-side (tool_use_failed 400s).
56
+ validatesToolArgs: true,
57
+ supportsEmbedding: false
58
+ },
59
+ adapter: "openai-compatible",
60
+ // tool_use_failed / json_validate_failed 400s carry the model's
61
+ // attempt in failed_generation — recover and route it.
62
+ wire: ["failed-generation"],
63
+ // Reasoning models stream their trace only when asked to parse it out;
64
+ // `reasoning_format` is rejected on the non-reasoning ones, which is why
65
+ // this is opt-in (see reasoningRequest above).
66
+ reasoningRequest: { reasoning_format: "parsed" }
67
+ },
68
+ openai: {
69
+ id: "openai",
70
+ baseUrl: "https://api.openai.com/v1",
71
+ envKey: "OPENAI_API_KEY",
72
+ defaultModel: "gpt-4o",
73
+ endpoint: {
74
+ nativeTools: true,
75
+ nativeJsonMode: true,
76
+ validatesToolArgs: false,
77
+ supportsEmbedding: true
78
+ },
79
+ adapter: "openai-compatible",
80
+ // 4o-era models occasionally emit JSONL where one value was asked
81
+ // for; the acceptance gate decides, so this can never misfire.
82
+ wire: ["jsonl-lines"]
83
+ },
84
+ openrouter: {
85
+ id: "openrouter",
86
+ baseUrl: "https://openrouter.ai/api/v1",
87
+ envKey: "OPENROUTER_API_KEY",
88
+ defaultModel: "openai/gpt-4o",
89
+ endpoint: {
90
+ nativeTools: true,
91
+ nativeJsonMode: true,
92
+ validatesToolArgs: false,
93
+ supportsEmbedding: false
94
+ },
95
+ adapter: "openai-compatible",
96
+ // OpenRouter unifies reasoning across the models it routes; `enabled` turns
97
+ // the trace on for those that have one and is ignored by those that don't.
98
+ reasoningRequest: { reasoning: { enabled: true } }
99
+ },
100
+ anthropic: {
101
+ id: "anthropic",
102
+ baseUrl: "https://api.anthropic.com/v1",
103
+ envKey: "ANTHROPIC_API_KEY",
104
+ defaultModel: "claude-sonnet-4-20250514",
105
+ endpoint: {
106
+ nativeTools: true,
107
+ nativeJsonMode: true,
108
+ validatesToolArgs: false,
109
+ supportsEmbedding: false
110
+ },
111
+ adapter: "anthropic"
112
+ },
113
+ google: {
114
+ id: "google",
115
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
116
+ envKey: "GOOGLE_API_KEY",
117
+ defaultModel: "gemini-2.0-flash",
118
+ endpoint: {
119
+ nativeTools: true,
120
+ nativeJsonMode: true,
121
+ validatesToolArgs: false,
122
+ supportsEmbedding: false
123
+ },
124
+ adapter: "google"
125
+ },
126
+ typesafe: {
127
+ id: "typesafe",
128
+ baseUrl: "https://api.typesafe.ai/v1",
129
+ envKey: "TYPESAFE_API_KEY",
130
+ // The moving alias. A caller that records calibration against a model pins
131
+ // a version (`jev-1.13.0`) with `.model()`.
132
+ defaultModel: "jev-latest",
133
+ adapter: "systemone"
134
+ }
135
+ };
136
+ var modelRegistry = {
137
+ // ── groq ──
138
+ "groq/qwen/qwen3.8-27b": {
139
+ capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: false, manglesNestedToolArgs: true, multimodal: true },
140
+ // nested: 3 of 20 trials failed
141
+ reasoningEfforts: ["none", "default", "low", "medium", "high"],
142
+ verified: "2026-09-24"
143
+ },
144
+ "groq/openai/gpt-oss-120b": {
145
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: false, manglesNestedToolArgs: true, multimodal: false },
146
+ // nested: 1 of 20 trials failed
147
+ reasoningEfforts: ["low", "medium", "high"],
148
+ verified: "2026-09-24"
149
+ },
150
+ "groq/openai/gpt-oss-20b": {
151
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: false, manglesNestedToolArgs: true, multimodal: false },
152
+ // nested: 6 of 20 trials failed
153
+ reasoningEfforts: ["low", "medium", "high"],
154
+ verified: "2026-09-24"
155
+ },
156
+ // ── openai ──
157
+ "openai/gpt-4o": {
158
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
159
+ // nested: 0 of 20 trials failed
160
+ reasoningEfforts: [],
161
+ verified: "2026-09-24"
162
+ },
163
+ "openai/gpt-4o-mini": {
164
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
165
+ // nested: 0 of 20 trials failed
166
+ reasoningEfforts: [],
167
+ verified: "2026-09-24"
168
+ },
169
+ "openai/gpt-4.1-mini": {
170
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
171
+ // nested: 0 of 20 trials failed
172
+ reasoningEfforts: [],
173
+ verified: "2026-09-24"
174
+ },
175
+ // ── openrouter ──
176
+ "openrouter/qwen/qwen3.8-27b": {
177
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
178
+ // nested: 0 of 20 trials failed
179
+ reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
180
+ verified: "2026-09-24"
181
+ },
182
+ "openrouter/openai/gpt-4o": {
183
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
184
+ // nested: 0 of 20 trials failed
185
+ reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
186
+ verified: "2026-09-24"
187
+ },
188
+ "openrouter/openai/gpt-4o-mini": {
189
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
190
+ // nested: 0 of 20 trials failed
191
+ reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
192
+ verified: "2026-09-24"
193
+ },
194
+ "openrouter/openai/gpt-oss-120b": {
195
+ capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: false },
196
+ // nested: 0 of 20 trials failed
197
+ reasoningEfforts: ["minimal", "low", "medium", "high", "xhigh", "max"],
198
+ verified: "2026-09-24"
199
+ },
200
+ "openrouter/z-ai/glm-5.2": {
201
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: true, multimodal: false },
202
+ // nested: 1 of 20 trials failed
203
+ reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
204
+ verified: "2026-09-24"
205
+ },
206
+ "openrouter/openai/gpt-5.6-luna": {
207
+ capabilities: { nativeJsonSchema: true, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: true },
208
+ // nested: 0 of 20 trials failed
209
+ reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
210
+ verified: "2026-09-24"
211
+ },
212
+ "openrouter/openai/gpt-oss-20b": {
213
+ capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: true, manglesNestedToolArgs: false, multimodal: false },
214
+ // nested: 0 of 20 trials failed
215
+ reasoningEfforts: ["minimal", "low", "medium", "high", "xhigh", "max"],
216
+ verified: "2026-09-24"
217
+ },
218
+ "openrouter/meta-llama/llama-3.3-70b-instruct": {
219
+ capabilities: { nativeJsonSchema: false, toolsWithStructuredOutput: true, manglesNestedToolArgs: true, multimodal: false },
220
+ // nested: 9 of 20 trials failed
221
+ reasoningEfforts: ["none", "minimal", "low", "medium", "high", "xhigh", "max"],
222
+ verified: "2026-09-24"
223
+ }
224
+ };
225
+ var UNMEASURED_MODEL = {
226
+ nativeJsonSchema: false,
227
+ toolsWithStructuredOutput: false,
228
+ manglesNestedToolArgs: true,
229
+ multimodal: false
230
+ };
231
+ var modelKey = (provider, model) => `${provider}/${model}`;
232
+ var modelEntry = (provider, model) => modelRegistry[modelKey(provider, model)];
233
+ var resolveApiKey = (envKey, explicitKey) => explicitKey ?? (typeof process !== "undefined" ? process.env?.[envKey] : void 0);
234
+ var chatProviderEntry = (id) => {
235
+ const entry = providerRegistry[id];
236
+ return entry === void 0 || entry.adapter === "systemone" ? void 0 : entry;
237
+ };
238
+
239
+ // src/utils/sdk-loader.ts
240
+ var loadSdk = async (pkg) => {
241
+ try {
242
+ const mod = await import(
243
+ /* @vite-ignore */
244
+ pkg
245
+ );
246
+ return mod.default ?? mod;
247
+ } catch {
248
+ throw new SignalError(
249
+ `Missing dependency: ${pkg}. Install it with: pnpm add ${pkg}`,
250
+ ErrorCode.MISSING_SDK,
251
+ { package: pkg }
252
+ );
253
+ }
254
+ };
255
+
256
+ // src/adapters/openai-compatible.adapter.ts
257
+ var translateContentParts = (parts) => parts.map((part) => {
258
+ if (part.type === "text") return { type: "text", text: part.text };
259
+ if (part.type === "image") {
260
+ const url = part.source.type === "url" ? part.source.url : `data:${part.source.mediaType};base64,${part.source.data}`;
261
+ return { type: "image_url", image_url: { url } };
262
+ }
263
+ return { type: "text", text: "" };
264
+ });
265
+ var translateMessage = (msg) => {
266
+ switch (msg.role) {
267
+ case "system":
268
+ return { role: "system", content: msg.content };
269
+ case "user":
270
+ return {
271
+ role: "user",
272
+ content: typeof msg.content === "string" ? msg.content : translateContentParts(msg.content)
273
+ };
274
+ case "assistant": {
275
+ const out = { role: "assistant", content: msg.content };
276
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
277
+ out.tool_calls = msg.toolCalls.map((tc) => ({
278
+ id: tc.id,
279
+ type: "function",
280
+ function: { name: tc.name, arguments: tc.args }
281
+ }));
282
+ }
283
+ return out;
284
+ }
285
+ case "tool":
286
+ return { role: "tool", content: msg.content, tool_call_id: msg.toolCallId };
287
+ default:
288
+ return { role: "user", content: "" };
289
+ }
290
+ };
291
+ var bindAt = (obj, path) => {
292
+ let parent;
293
+ let current = obj;
294
+ for (const key of path) {
295
+ if (!current || typeof current !== "object") return void 0;
296
+ parent = current;
297
+ current = current[key];
298
+ }
299
+ return typeof current === "function" ? current.bind(parent) : void 0;
300
+ };
301
+ var createSdkInstance = async (config) => {
302
+ const Sdk = await loadSdk("openai");
303
+ const Constructor = typeof Sdk === "function" ? Sdk : Sdk["default"];
304
+ return new Constructor({
305
+ apiKey: config.apiKey,
306
+ baseURL: config.baseUrl,
307
+ dangerouslyAllowBrowser: typeof globalThis.window !== "undefined"
308
+ });
309
+ };
310
+ var loadSdkFn = async (config, path, missing, strictClient) => {
311
+ if (config.client) {
312
+ const fn2 = bindAt(config.client, path);
313
+ if (fn2) return fn2;
314
+ if (strictClient) {
315
+ throw new SignalError(`Invalid client: expected client.${path.join(".")} to be a function`, ErrorCode.PROVIDER_ERROR);
316
+ }
317
+ }
318
+ const fn = bindAt(await createSdkInstance(config), path);
319
+ if (!fn) throw new SignalError(missing.message, missing.code);
320
+ return fn;
321
+ };
322
+ var loadChatCreateFn = (config) => loadSdkFn(config, ["chat", "completions", "create"], { message: "Failed to initialize OpenAI SDK", code: ErrorCode.MISSING_SDK }, true);
323
+ var loadEmbedCreateFn = (config) => loadSdkFn(
324
+ config,
325
+ ["embeddings", "create"],
326
+ { message: "Embedding is not supported by this provider or SDK", code: ErrorCode.PROVIDER_ERROR },
327
+ false
328
+ );
329
+ var createOpenAICompatibleAdapter = async (config) => {
330
+ const chatCreate = await loadChatCreateFn(config);
331
+ const buildParams = (request) => {
332
+ const params = {
333
+ model: request.model,
334
+ messages: request.messages.map(translateMessage)
335
+ };
336
+ if (request.responseFormat) {
337
+ params["response_format"] = request.responseFormat.type === "json_schema" ? { type: "json_schema", json_schema: request.responseFormat.jsonSchema } : { type: "json_object" };
338
+ }
339
+ if (request.tools?.length) params["tools"] = request.tools;
340
+ if (request.toolChoice !== void 0) {
341
+ params["tool_choice"] = typeof request.toolChoice === "string" ? request.toolChoice : { type: "function", function: { name: request.toolChoice.name } };
342
+ }
343
+ if (request.options?.temperature !== void 0) params["temperature"] = request.options.temperature;
344
+ if (request.options?.maxTokens !== void 0) params["max_completion_tokens"] = request.options.maxTokens;
345
+ if (request.options?.topP !== void 0) params["top_p"] = request.options.topP;
346
+ if (request.options?.stopSequences) params["stop"] = request.options.stopSequences;
347
+ if (request.options?.seed !== void 0) params["seed"] = request.options.seed;
348
+ if (request.options?.reasoningEffort !== void 0)
349
+ params["reasoning_effort"] = request.options.reasoningEffort;
350
+ return params;
351
+ };
352
+ const chat = async (request) => {
353
+ const params = buildParams(request);
354
+ let completion;
355
+ try {
356
+ completion = await chatCreate(params);
357
+ } catch (error) {
358
+ throw new SignalError(
359
+ `Provider error: ${error instanceof Error ? error.message : String(error)}`,
360
+ ErrorCode.PROVIDER_ERROR,
361
+ { raw: error }
362
+ );
363
+ }
364
+ const choice = completion.choices?.[0];
365
+ return {
366
+ content: choice?.message?.content ?? "",
367
+ toolCalls: choice?.message?.tool_calls?.map((tc) => ({
368
+ id: tc.id,
369
+ name: tc.function.name,
370
+ args: tc.function.arguments
371
+ })),
372
+ usage: {
373
+ inputTokens: completion.usage?.prompt_tokens ?? 0,
374
+ outputTokens: completion.usage?.completion_tokens ?? 0,
375
+ totalTokens: completion.usage?.total_tokens ?? 0
376
+ },
377
+ finishReason: choice?.finish_reason ?? "unknown",
378
+ raw: completion
379
+ };
380
+ };
381
+ async function* chatStream(request, options) {
382
+ const streamParams = {
383
+ ...buildParams(request),
384
+ // Ask for the reasoning trace only when the caller wants reasoning — the
385
+ // opt-in that keeps `reasoning_format` off a plain Groq call. Stream-only:
386
+ // the non-stream `chat` path never sees these, so its content is unmoved.
387
+ ...request.options?.reasoningEffort !== void 0 && config.reasoningRequest !== void 0 ? config.reasoningRequest : {},
388
+ stream: true,
389
+ stream_options: { include_usage: true }
390
+ };
391
+ let sseStream;
392
+ try {
393
+ sseStream = await chatCreate(streamParams, options);
394
+ } catch (error) {
395
+ throw new SignalError(
396
+ `Provider stream error: ${error instanceof Error ? error.message : String(error)}`,
397
+ ErrorCode.PROVIDER_ERROR,
398
+ { raw: error }
399
+ );
400
+ }
401
+ try {
402
+ for await (const chunk of sseStream) {
403
+ const choice = chunk.choices?.[0];
404
+ if (choice?.delta?.content) {
405
+ yield { type: "text", text: choice.delta.content };
406
+ }
407
+ const reasoning = choice?.delta?.reasoning ?? choice?.delta?.reasoning_content;
408
+ if (reasoning) {
409
+ yield { type: "reasoning", text: reasoning };
410
+ }
411
+ if (choice?.delta?.tool_calls) {
412
+ for (const tc of choice.delta.tool_calls) {
413
+ yield {
414
+ type: "tool_call",
415
+ index: tc.index,
416
+ id: tc.id,
417
+ name: tc.function?.name,
418
+ argsFragment: tc.function?.arguments
419
+ };
420
+ }
421
+ }
422
+ if (chunk.usage) {
423
+ yield {
424
+ type: "usage",
425
+ inputTokens: chunk.usage.prompt_tokens ?? 0,
426
+ outputTokens: chunk.usage.completion_tokens ?? 0,
427
+ totalTokens: chunk.usage.total_tokens ?? 0
428
+ };
429
+ }
430
+ if (choice?.finish_reason) {
431
+ yield { type: "finish", finishReason: choice.finish_reason };
432
+ }
433
+ }
434
+ } catch (error) {
435
+ if (error instanceof SignalError) throw error;
436
+ throw new SignalError(
437
+ `Provider stream error: ${error instanceof Error ? error.message : String(error)}`,
438
+ ErrorCode.PROVIDER_ERROR,
439
+ { raw: error }
440
+ );
441
+ }
442
+ }
443
+ let cachedEmbedCreate;
444
+ const embed = async (request) => {
445
+ if (!cachedEmbedCreate) cachedEmbedCreate = await loadEmbedCreateFn(config);
446
+ const params = {
447
+ model: request.model,
448
+ input: request.input
449
+ };
450
+ if (request.dimensions !== void 0) params["dimensions"] = request.dimensions;
451
+ let response;
452
+ try {
453
+ response = await cachedEmbedCreate(params);
454
+ } catch (error) {
455
+ throw new SignalError(
456
+ `Embedding error: ${error instanceof Error ? error.message : String(error)}`,
457
+ ErrorCode.PROVIDER_ERROR,
458
+ { raw: error }
459
+ );
460
+ }
461
+ const embeddings = (response.data ?? []).sort((a, b) => a.index - b.index).map((d) => d.embedding);
462
+ return {
463
+ embeddings,
464
+ usage: {
465
+ inputTokens: response.usage?.prompt_tokens ?? 0,
466
+ totalTokens: response.usage?.total_tokens ?? 0
467
+ }
468
+ };
469
+ };
470
+ return { kind: "chat", id: "openai-compatible", chat, chatStream, embed };
471
+ };
472
+
473
+ // src/adapters/anthropic.adapter.ts
474
+ var createAnthropicAdapter = async (_config) => {
475
+ throw new SignalError(
476
+ "Anthropic adapter is not yet implemented. Use openai-compatible with OpenRouter for Anthropic models.",
477
+ ErrorCode.PROVIDER_ERROR
478
+ );
479
+ };
480
+
481
+ // src/adapters/google.adapter.ts
482
+ var createGoogleAdapter = async (_config) => {
483
+ throw new SignalError(
484
+ "Google adapter is not yet implemented. Use openai-compatible with OpenRouter for Google models.",
485
+ ErrorCode.PROVIDER_ERROR
486
+ );
487
+ };
488
+ var InjectedClientSchema = zod.z.object({
489
+ fetch: zod.z.custom((value) => typeof value === "function")
490
+ });
491
+ var BodySchema = zod.z.object({
492
+ model: zod.z.string().optional(),
493
+ answers: zod.z.unknown(),
494
+ usage: zod.z.object({ input_tokens: zod.z.number(), output_tokens: zod.z.number() }).optional()
495
+ });
496
+ var sendOf = (client) => {
497
+ if (client === void 0) return fetch;
498
+ const injected = InjectedClientSchema.safeParse(client);
499
+ if (!injected.success) {
500
+ throw new SignalError("Invalid client: expected client.fetch to be a function", ErrorCode.PROVIDER_ERROR);
501
+ }
502
+ return injected.data.fetch;
503
+ };
504
+ var jsonOrText = (text) => {
505
+ try {
506
+ return JSON.parse(text);
507
+ } catch {
508
+ return text;
509
+ }
510
+ };
511
+ var providerError = (message, raw) => new SignalError(`Provider error: ${message}`, ErrorCode.PROVIDER_ERROR, { raw });
512
+ var createSystemOneAdapter = async (config) => {
513
+ const send = sendOf(config.client);
514
+ const url = `${config.baseUrl.replace(/\/+$/, "")}/systemone`;
515
+ const decide = async (request, options) => {
516
+ let status;
517
+ let body;
518
+ try {
519
+ const response = await send(url, {
520
+ method: "POST",
521
+ headers: { authorization: `Bearer ${config.apiKey}`, "content-type": "application/json" },
522
+ body: JSON.stringify({ model: request.model, state: request.state, questions: request.questions }),
523
+ ...options?.signal !== void 0 && { signal: options.signal }
524
+ });
525
+ status = response.status;
526
+ body = jsonOrText(await response.text());
527
+ } catch (error) {
528
+ throw providerError(error instanceof Error ? error.message : String(error), error);
529
+ }
530
+ if (status < 200 || status >= 300) throw providerError(`HTTP ${status}`, { status, body });
531
+ const parsed = BodySchema.safeParse(body);
532
+ if (!parsed.success) return { answers: void 0, raw: body };
533
+ return {
534
+ ...parsed.data.model !== void 0 && { model: parsed.data.model },
535
+ answers: parsed.data.answers,
536
+ ...parsed.data.usage !== void 0 && {
537
+ usage: { inputTokens: parsed.data.usage.input_tokens, outputTokens: parsed.data.usage.output_tokens }
538
+ },
539
+ raw: body
540
+ };
541
+ };
542
+ return { kind: "decisions", id: "systemone", decide };
543
+ };
544
+
545
+ // src/transport/protocol.ts
546
+ var RESPOND_DESCRIPTION = "Finish the task. Call this EXACTLY ONCE, ALONE in its own turn, with your final envelope: { response?: string (human-facing text), data (the payload), reasoning?: string (why) }. Calling this ends the run.";
547
+ var EMIT_WITH_TOOLS = (shape, note) => `FINISH PROTOCOL: Use your tools as needed. To finish, your ENTIRE final message must be ONLY the JSON envelope ${shape} \u2014 raw JSON, no prose around it, no code fences.${note} A message without tool calls is treated as your final envelope.`;
548
+ var EMIT_TOOLLESS = (shape, note) => `FINISH PROTOCOL: Your ENTIRE final message must be ONLY the JSON envelope ${shape} \u2014 raw JSON, no prose around it, no code fences.${note}`;
549
+ var NATIVE = (shape, note) => `FINISH PROTOCOL: To finish, reply with no tool calls; your reply is the JSON envelope ${shape}.${note}`;
550
+ var RESPOND = (shape, note) => `FINISH PROTOCOL: To finish, call the \`respond\` tool ALONE in its own turn with the envelope ${shape} as its arguments \u2014 or reply with ONLY that JSON envelope as your entire message.${note}`;
551
+ var envelopeShape = (spec) => {
552
+ if (!spec.hasData) return '{ "reasoning"?: string, "response": string }';
553
+ return spec.responseMode === "required" ? '{ "reasoning"?: string, "response": string, "data": <the payload> }' : '{ "reasoning"?: string, "response"?: string, "data": <the payload> }';
554
+ };
555
+ var finishProtocol = (spec) => {
556
+ const shape = envelopeShape(spec);
557
+ const note = spec.hasData && spec.injectSchemaDoc ? " `data` must match the OUTPUT SCHEMA above." : "";
558
+ switch (spec.transport) {
559
+ case "emit":
560
+ return spec.hasTools ? EMIT_WITH_TOOLS(shape, note) : EMIT_TOOLLESS(shape, note);
561
+ case "native":
562
+ return NATIVE(shape, note);
563
+ case "respond":
564
+ return RESPOND(shape, note);
565
+ }
566
+ };
567
+ var RESPOND_TERMINATION = "You did not finish. Either call the `respond` tool with your final envelope ({ response?, data, reasoning? }), or reply with ONLY that JSON envelope as your entire message \u2014 no surrounding prose.";
568
+ var RESPOND_INVALID = "Your output was invalid: {issues}. Either call `respond`, or reply with ONLY the corrected JSON envelope \u2014 no prose.";
569
+ var CONTENT_TERMINATION = "You did not finish. Reply with ONLY the JSON envelope ({ response?, data, reasoning? }) as your entire message \u2014 no surrounding prose, no code fences.";
570
+ var CONTENT_INVALID = "Your output was invalid: {issues}. Reply with ONLY the corrected JSON envelope as your entire message.";
571
+ var corrections = (transport) => transport === "respond" ? { termination: RESPOND_TERMINATION, invalidOutput: RESPOND_INVALID } : { termination: CONTENT_TERMINATION, invalidOutput: CONTENT_INVALID };
572
+ var bareSchemaPrompt = (jsonSchema) => `OUTPUT SCHEMA: your final reply must be ONLY a JSON value matching this schema \u2014 raw JSON, no prose, no code fences:
573
+ ${jsonSchema}`;
574
+ var bareSchemaCorrection = (issues) => `Your output was invalid: ${issues}. Reply with ONLY the corrected JSON value matching the OUTPUT SCHEMA.`;
575
+ var decisionEmulationPrompt = (state) => `Answer every field of the OUTPUT SCHEMA about the STATE below. Each field's description is the question and lists everything you may answer with.
576
+ STATE:
577
+ ${state}`;
578
+ var decisionQuestionDescription = (question) => {
579
+ if (question.type === "choice") return `${question.instructions} Options: ${JSON.stringify(question.criteria)}`;
580
+ if (question.type === "score") {
581
+ return `${question.instructions} Answer with the index of the level that fits. Levels, lowest first: ${JSON.stringify(question.criteria)}`;
582
+ }
583
+ return question.criteria === void 0 ? question.instructions : `${question.instructions} Meaning: ${JSON.stringify(question.criteria)}`;
584
+ };
585
+
586
+ // src/decide/gate.ts
587
+ var probability = zod.z.number().min(0).max(1);
588
+ var enumOf = (name, options) => {
589
+ const [first, ...rest] = options;
590
+ if (first === void 0) {
591
+ throw new SignalError(`Question "${name}" offers nothing to answer with`, ErrorCode.VALIDATION_FAILED, { question: name });
592
+ }
593
+ return zod.z.enum([first, ...rest]);
594
+ };
595
+ var levelKeysOf = (question) => question.criteria.map((_, level) => String(level));
596
+ var mostProbable = (probabilities) => probabilities.reduce((best, value, level) => value > (probabilities[best] ?? 0) ? level : best, 0);
597
+ var calibratedGate = (name, question) => {
598
+ if (question.type === "choice") {
599
+ const option = enumOf(name, Object.keys(question.criteria));
600
+ return zod.z.object({ choice: option, probabilities: zod.z.record(option, probability), confidence: probability });
601
+ }
602
+ if (question.type === "score") {
603
+ const keys = levelKeysOf(question);
604
+ const level = enumOf(name, keys);
605
+ return zod.z.object({
606
+ score: zod.z.number().min(0).max(keys.length - 1),
607
+ probabilities: zod.z.record(level, probability),
608
+ confidence: probability
609
+ }).transform((answer) => {
610
+ const probabilities = keys.map((key) => answer.probabilities[key] ?? 0);
611
+ return { level: mostProbable(probabilities), score: answer.score, probabilities, confidence: answer.confidence };
612
+ });
613
+ }
614
+ return zod.z.object({ noul: probability }).transform((answer) => ({ answer: answer.noul >= 0.5, noul: answer.noul }));
615
+ };
616
+ var uncalibratedField = (name, question) => {
617
+ const description = decisionQuestionDescription(question);
618
+ if (question.type === "choice") return enumOf(name, Object.keys(question.criteria)).describe(description);
619
+ if (question.type === "score") return zod.z.number().int().min(0).max(question.criteria.length - 1).describe(description);
620
+ return zod.z.boolean().describe(description);
621
+ };
622
+ var uncalibratedDecision = (question, value) => {
623
+ if (question.type === "choice") return { choice: value };
624
+ if (question.type === "score") return { level: value };
625
+ return { answer: value };
626
+ };
627
+ var assertAsked = (questions) => {
628
+ if (Object.keys(questions).length === 0) {
629
+ throw new SignalError("decide() needs at least one question", ErrorCode.VALIDATION_FAILED);
630
+ }
631
+ };
632
+ var answersEvery = (questions, decisions) => Object.keys(questions).every((name) => name in decisions);
633
+ var rejected = (issues, raw) => new SignalError(`Decision rejected: ${issues.join("; ")}`, ErrorCode.VALIDATION_FAILED, { issues, raw });
634
+ var issuesOf = (name, error) => error.issues.map((issue) => `${[name, ...issue.path].join(".")}: ${issue.message}`);
635
+ var assertQuestions = (questions) => {
636
+ assertAsked(questions);
637
+ for (const [name, question] of Object.entries(questions)) calibratedGate(name, question);
638
+ };
639
+ var acceptCalibrated = (questions, answers, raw) => {
640
+ const arrived = zod.z.record(zod.z.string(), zod.z.unknown()).safeParse(answers);
641
+ if (!arrived.success) throw rejected(["the provider returned no answers"], raw);
642
+ const decisions = {};
643
+ const issues = [];
644
+ for (const [name, question] of Object.entries(questions)) {
645
+ if (!(name in arrived.data)) {
646
+ issues.push(`${name}: not answered`);
647
+ continue;
648
+ }
649
+ const gated = calibratedGate(name, question).safeParse(arrived.data[name]);
650
+ if (gated.success) decisions[name] = gated.data;
651
+ else issues.push(...issuesOf(name, gated.error));
652
+ }
653
+ if (issues.length > 0 || !answersEvery(questions, decisions)) throw rejected(issues, raw);
654
+ return decisions;
655
+ };
656
+ var uncalibratedSchemaOf = (questions) => zod.z.strictObject(
657
+ Object.fromEntries(Object.entries(questions).map(([name, question]) => [name, uncalibratedField(name, question)]))
658
+ );
659
+ var acceptUncalibrated = (questions, value, raw) => {
660
+ const gated = uncalibratedSchemaOf(questions).safeParse(value);
661
+ if (!gated.success) throw rejected(issuesOf("decisions", gated.error), raw);
662
+ const decisions = {};
663
+ for (const [name, question] of Object.entries(questions)) decisions[name] = uncalibratedDecision(question, gated.data[name]);
664
+ if (!answersEvery(questions, decisions)) throw rejected(["a question went unanswered"], raw);
665
+ return decisions;
666
+ };
667
+
668
+ // src/stream/execute-step-stream.ts
669
+ var assembleToolCall = (map, delta) => {
670
+ let tc = map.get(delta.index);
671
+ if (!tc) {
672
+ tc = { id: delta.id ?? "", name: delta.name ?? "", args: "" };
673
+ map.set(delta.index, tc);
674
+ }
675
+ if (delta.id) tc.id = delta.id;
676
+ if (delta.name) tc.name = delta.name;
677
+ if (delta.argsFragment) tc.args += delta.argsFragment;
678
+ };
679
+ var buildProviderRequest = (model, request) => {
680
+ const providerTools = request.tools && request.tools.length > 0 ? request.tools.map((tool) => ({
681
+ type: "function",
682
+ function: {
683
+ name: tool.name,
684
+ description: tool.description,
685
+ parameters: tool.parameters
686
+ }
687
+ })) : void 0;
688
+ return {
689
+ model,
690
+ messages: request.messages.slice(),
691
+ ...providerTools && { tools: providerTools },
692
+ ...request.toolChoice !== void 0 && { toolChoice: request.toolChoice },
693
+ ...request.responseFormat !== void 0 && { responseFormat: request.responseFormat },
694
+ ...request.options !== void 0 && { options: request.options }
695
+ };
696
+ };
697
+ async function* executeStepStream(config) {
698
+ const providerRequest = buildProviderRequest(config.model, config.request);
699
+ const abortSignal = config.streamOptions?.signal;
700
+ let contentBuffer = "";
701
+ let finishReason = "unknown";
702
+ const assembledToolCalls = /* @__PURE__ */ new Map();
703
+ let totalUsage = { inputTokens: 0, outputTokens: 0, totalTokens: 0, reported: false };
704
+ try {
705
+ for await (const delta of config.adapter.chatStream(providerRequest, abortSignal ? { signal: abortSignal } : void 0)) {
706
+ if (abortSignal?.aborted) return;
707
+ switch (delta.type) {
708
+ case "text":
709
+ contentBuffer += delta.text;
710
+ yield { type: "text", text: delta.text };
711
+ break;
712
+ // Reasoning is passed straight through and deliberately NOT added to
713
+ // contentBuffer: it is the model thinking, not its answer.
714
+ case "reasoning":
715
+ yield { type: "reasoning", text: delta.text };
716
+ break;
717
+ case "tool_call":
718
+ assembleToolCall(assembledToolCalls, delta);
719
+ yield {
720
+ type: "tool_call_delta",
721
+ index: delta.index,
722
+ ...delta.id !== void 0 && { id: delta.id },
723
+ ...delta.name !== void 0 && { name: delta.name },
724
+ argsText: delta.argsFragment ?? ""
725
+ };
726
+ break;
727
+ case "usage":
728
+ totalUsage = {
729
+ inputTokens: delta.inputTokens,
730
+ outputTokens: delta.outputTokens,
731
+ totalTokens: delta.totalTokens,
732
+ reported: true
733
+ };
734
+ break;
735
+ case "finish":
736
+ finishReason = delta.finishReason;
737
+ break;
738
+ }
739
+ }
740
+ } catch (error) {
741
+ if (abortSignal?.aborted) return;
742
+ throw error;
743
+ }
744
+ const toolCalls = [...assembledToolCalls.values()].map((tc) => {
745
+ let parsed = tc.args;
746
+ if (typeof tc.args === "string" && tc.args.length > 0) {
747
+ try {
748
+ parsed = JSON.parse(tc.args);
749
+ } catch {
750
+ parsed = tc.args;
751
+ }
752
+ }
753
+ return { id: tc.id, name: tc.name, args: parsed };
754
+ });
755
+ const result = {
756
+ content: contentBuffer,
757
+ toolCalls,
758
+ usage: totalUsage.reported ? totalUsage : estimateUsage(providerRequest.messages, contentBuffer, toolCalls),
759
+ finishReason,
760
+ raw: null
761
+ };
762
+ yield { type: "done", result };
763
+ }
764
+
765
+ // src/output-trust.ts
766
+ var asOutput = (content) => content;
767
+
768
+ // src/run.ts
769
+ var MAX_TOOL_TURNS = 10;
770
+ var ZERO = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
771
+ var stringify = (value) => {
772
+ if (typeof value === "string") return value;
773
+ try {
774
+ return JSON.stringify(value) ?? String(value);
775
+ } catch {
776
+ return String(value);
777
+ }
778
+ };
779
+ var toJsonSchema = (schema) => schema["~standard"].jsonSchema.output({ target: "draft-07" });
780
+ var buildRequest = (spec, caps) => {
781
+ const tools = spec.tools ?? [];
782
+ if (tools.length > 0 && !caps.nativeTools) {
783
+ throw new SignalError(
784
+ "This provider has no native tool calling (capabilities.nativeTools) \u2014 tools are unavailable here.",
785
+ ErrorCode.PROVIDER_ERROR
786
+ );
787
+ }
788
+ const descriptors = tools.map((tool) => ({
789
+ name: tool.name,
790
+ description: tool.description,
791
+ parameters: toJsonSchema(tool.inputSchema)
792
+ }));
793
+ const grammarViable = spec.schema !== void 0 && caps.nativeJsonSchema && (tools.length === 0 || caps.toolsWithStructuredOutput);
794
+ return {
795
+ ...descriptors.length > 0 && { tools: descriptors },
796
+ ...spec.schema && { output: { accept: spec.schema } },
797
+ ...grammarViable && spec.schema ? {
798
+ responseFormat: {
799
+ type: "json_schema",
800
+ jsonSchema: { name: "output", strict: false, schema: toJsonSchema(spec.schema) }
801
+ }
802
+ } : spec.schema !== void 0 && caps.nativeJsonMode && tools.length === 0 ? { responseFormat: { type: "json_object" } } : {},
803
+ ...spec.options && { options: spec.options }
804
+ };
805
+ };
806
+ var buildMeta = (model, usage, start, retries, toolCalls, raws) => ({
807
+ model,
808
+ usage,
809
+ durationMs: Date.now() - start,
810
+ retries,
811
+ toolCalls,
812
+ provider: { raw: raws.length === 1 ? raws[0] : raws, errors: [] }
813
+ });
814
+ async function* runStream(spec, deps) {
815
+ const start = Date.now();
816
+ const requestBase = buildRequest(spec, deps.capabilities);
817
+ const toolMap = new Map((spec.tools ?? []).map((tool) => [tool.name, tool]));
818
+ const messages = [...spec.messages];
819
+ if (spec.schema && requestBase.responseFormat?.type !== "json_schema") {
820
+ messages.push({ role: "system", content: bareSchemaPrompt(JSON.stringify(toJsonSchema(spec.schema))) });
821
+ }
822
+ let usage = ZERO;
823
+ let retries = 0;
824
+ const records = [];
825
+ const raws = [];
826
+ for (let turn = 0; turn < MAX_TOOL_TURNS + spec.retries; turn += 1) {
827
+ let result;
828
+ for await (const event of deps.stepStream({ ...requestBase, messages }, spec.streamOptions)) {
829
+ if (event.type === "text") yield { type: "text", text: event.text };
830
+ if (event.type === "done") result = event.result;
831
+ }
832
+ if (!result) throw new SignalError("stream ended without a done event", ErrorCode.PROVIDER_ERROR);
833
+ usage = {
834
+ inputTokens: usage.inputTokens + result.usage.inputTokens,
835
+ outputTokens: usage.outputTokens + result.usage.outputTokens,
836
+ totalTokens: usage.totalTokens + result.usage.totalTokens
837
+ };
838
+ raws.push(result.raw);
839
+ const calls = result.outcome?.kind === "tool_calls" ? result.outcome.calls : spec.schema ? [] : result.toolCalls;
840
+ if (calls.length > 0) {
841
+ messages.push({
842
+ role: "assistant",
843
+ content: result.content,
844
+ toolCalls: calls.map((call) => ({ id: call.id, name: call.name, args: stringify(call.args) }))
845
+ });
846
+ for (const call of calls) {
847
+ spec.onToolCall?.(call.name, call.args);
848
+ yield { type: "tool_start", name: call.name, args: call.args };
849
+ const tool = toolMap.get(call.name);
850
+ const parsed = tool?.inputSchema.safeParse(call.args);
851
+ const started = Date.now();
852
+ let output2;
853
+ if (!tool) output2 = `error: unknown tool "${call.name}"`;
854
+ else if (parsed && !parsed.success) {
855
+ output2 = `error: input_invalid: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`;
856
+ } else {
857
+ try {
858
+ output2 = await tool.execute(parsed?.data);
859
+ } catch (cause) {
860
+ output2 = `error: ${cause instanceof Error ? cause.message : String(cause)}`;
861
+ }
862
+ }
863
+ records.push({ name: call.name, args: call.args, result: output2, durationMs: Date.now() - started });
864
+ yield { type: "tool_end", name: call.name, result: output2 };
865
+ messages.push({ role: "tool", toolCallId: call.id, name: call.name, content: stringify(output2) });
866
+ }
867
+ continue;
868
+ }
869
+ if (!spec.schema) {
870
+ const history = [...messages, { role: "assistant", content: result.content }];
871
+ yield {
872
+ type: "done",
873
+ response: asOutput(result.content),
874
+ history,
875
+ meta: buildMeta(deps.model, usage, start, retries, records, raws)
876
+ };
877
+ return;
878
+ }
879
+ if (result.outcome?.kind === "output") {
880
+ const parsed = spec.schema.safeParse(result.outcome.value);
881
+ if (parsed.success) {
882
+ const history = [...messages, { role: "assistant", content: result.content }];
883
+ yield {
884
+ type: "done",
885
+ response: parsed.data,
886
+ history,
887
+ meta: buildMeta(deps.model, usage, start, retries, records, raws)
888
+ };
889
+ return;
890
+ }
891
+ }
892
+ const evidence = result.outcome?.kind === "failed" ? result.outcome.evidence : "output failed schema validation";
893
+ retries += 1;
894
+ const error = new SignalError(`Response failed schema validation: ${evidence}`, ErrorCode.VALIDATION_FAILED);
895
+ spec.onRetry?.(error, retries);
896
+ if (retries > spec.retries) {
897
+ yield { type: "error", error, recovered: false };
898
+ return;
899
+ }
900
+ yield { type: "retry", reason: evidence, attempt: retries };
901
+ if (result.content.length > 0) messages.push({ role: "assistant", content: result.content });
902
+ messages.push({ role: "system", content: bareSchemaCorrection(evidence) });
903
+ }
904
+ throw new SignalError("Max tool iterations reached", ErrorCode.MAX_ITERATIONS);
905
+ }
906
+ var runComplete = async (spec, deps) => {
907
+ for await (const event of runStream(spec, deps)) {
908
+ if (event.type === "done") return { response: event.response, history: event.history, meta: event.meta };
909
+ if (event.type === "error") throw event.error;
910
+ }
911
+ throw new SignalError("run ended without a result", ErrorCode.PROVIDER_ERROR);
912
+ };
913
+
914
+ // src/wire/repair.ts
915
+ var INVISIBLES = /[​‌‍⁠]/g;
916
+ var tryParse = (text) => {
917
+ try {
918
+ return { ok: true, value: JSON.parse(text) };
919
+ } catch {
920
+ return { ok: false };
921
+ }
922
+ };
923
+ var spanCandidates = (text, open, close) => {
924
+ const first = text.indexOf(open);
925
+ if (first < 0) return [];
926
+ const candidates = [];
927
+ for (let last = text.lastIndexOf(close); last > first; last = text.lastIndexOf(close, last - 1)) {
928
+ candidates.push(text.slice(first, last + 1));
929
+ if (candidates.length >= 8) break;
930
+ }
931
+ return candidates;
932
+ };
933
+ var extractJson = (content) => {
934
+ const cleaned = content.replace(INVISIBLES, "").trim();
935
+ if (cleaned.length === 0) return { ok: false };
936
+ const direct = tryParse(cleaned);
937
+ if (direct.ok) return direct;
938
+ const fence = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
939
+ const body = fence?.[1] !== void 0 ? fence[1].trim() : cleaned;
940
+ if (body !== cleaned) {
941
+ const fenced = tryParse(body);
942
+ if (fenced.ok) return fenced;
943
+ }
944
+ for (const candidate of [...spanCandidates(body, "{", "}"), ...spanCandidates(body, "[", "]")]) {
945
+ const parsed = tryParse(candidate);
946
+ if (parsed.ok) return parsed;
947
+ }
948
+ return { ok: false };
949
+ };
950
+ var repairEscapeDamage = (text) => {
951
+ const candidates = [];
952
+ const noQuoteEscapes = text.replace(/\\'/g, "'");
953
+ if (noQuoteEscapes !== text) candidates.push(noQuoteEscapes);
954
+ const unescaped = text.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
955
+ if (unescaped !== text) candidates.push(unescaped);
956
+ return candidates;
957
+ };
958
+ var decodeJsonish = (value) => {
959
+ if (typeof value !== "string") return value;
960
+ const trimmed = value.trim();
961
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return value;
962
+ const direct = tryParse(trimmed);
963
+ if (direct.ok) return direct.value;
964
+ for (const repaired of repairEscapeDamage(trimmed)) {
965
+ const parsed = tryParse(repaired);
966
+ if (parsed.ok) return parsed.value;
967
+ }
968
+ return value;
969
+ };
970
+ var deepDecodeJsonish = (value) => {
971
+ if (typeof value === "string") {
972
+ const decoded = decodeJsonish(value);
973
+ return decoded === value ? value : deepDecodeJsonish(decoded);
974
+ }
975
+ if (Array.isArray(value)) return value.map(deepDecodeJsonish);
976
+ if (typeof value === "object" && value !== null) {
977
+ const out = {};
978
+ for (const [key, entry] of Object.entries(value)) out[key] = deepDecodeJsonish(entry);
979
+ return out;
980
+ }
981
+ return value;
982
+ };
983
+ var LITERAL_CHARS = /[0-9a-zA-Z.+\-]/;
984
+ var isCompleteLiteral = (buf) => {
985
+ if (buf === "true" || buf === "false" || buf === "null") return true;
986
+ return buf.length > 0 && !Number.isNaN(Number(buf));
987
+ };
988
+ var scan = (text) => {
989
+ const stack = [];
990
+ let inString = false;
991
+ let escape = false;
992
+ let stringIsValue = false;
993
+ let literal = "";
994
+ let boundary;
995
+ let started = false;
996
+ const closers = () => [...stack].reverse().map((frame) => frame.type === "obj" ? "}" : "]").join("");
997
+ const markBoundary = (end) => {
998
+ boundary = { end, closers: closers() };
999
+ };
1000
+ const valueDone = (end) => {
1001
+ const top = stack[stack.length - 1];
1002
+ if (top) top.expectingValue = false;
1003
+ markBoundary(end);
1004
+ };
1005
+ const endLiteral = (end) => {
1006
+ if (literal.length === 0) return;
1007
+ if (isCompleteLiteral(literal)) valueDone(end);
1008
+ literal = "";
1009
+ };
1010
+ for (let i = 0; i < text.length; i += 1) {
1011
+ const ch = text[i];
1012
+ if (inString) {
1013
+ if (escape) {
1014
+ escape = false;
1015
+ continue;
1016
+ }
1017
+ if (ch === "\\") {
1018
+ escape = true;
1019
+ continue;
1020
+ }
1021
+ if (ch === '"') {
1022
+ inString = false;
1023
+ if (stringIsValue) valueDone(i + 1);
1024
+ }
1025
+ continue;
1026
+ }
1027
+ if (literal.length > 0 && !LITERAL_CHARS.test(ch)) endLiteral(i);
1028
+ switch (ch) {
1029
+ case "{": {
1030
+ started = true;
1031
+ stack.push({ type: "obj", expectingValue: false });
1032
+ break;
1033
+ }
1034
+ case "[": {
1035
+ started = true;
1036
+ stack.push({ type: "arr", expectingValue: true });
1037
+ break;
1038
+ }
1039
+ case "}":
1040
+ case "]": {
1041
+ if (stack.length === 0) return void 0;
1042
+ stack.pop();
1043
+ valueDone(i + 1);
1044
+ break;
1045
+ }
1046
+ case '"': {
1047
+ const top = stack[stack.length - 1];
1048
+ inString = true;
1049
+ escape = false;
1050
+ stringIsValue = top === void 0 || top.type === "arr" || top.expectingValue;
1051
+ break;
1052
+ }
1053
+ case ":": {
1054
+ const top = stack[stack.length - 1];
1055
+ if (top?.type === "obj") top.expectingValue = true;
1056
+ break;
1057
+ }
1058
+ case ",":
1059
+ break;
1060
+ default: {
1061
+ if (LITERAL_CHARS.test(ch)) {
1062
+ started = true;
1063
+ literal += ch;
1064
+ }
1065
+ break;
1066
+ }
1067
+ }
1068
+ }
1069
+ if (!inString) endLiteral(text.length);
1070
+ if (!started) return void 0;
1071
+ return { openAtEnd: stack.length + (inString ? 1 : 0), ...boundary && { boundary } };
1072
+ };
1073
+ var closeTruncated = (content) => {
1074
+ const cleaned = content.replace(INVISIBLES, "").trim();
1075
+ if (!cleaned.startsWith("{") && !cleaned.startsWith("[")) return void 0;
1076
+ if (tryParse(cleaned).ok) return void 0;
1077
+ const result = scan(cleaned);
1078
+ if (!result?.boundary || result.openAtEnd === 0) return void 0;
1079
+ const candidate = cleaned.slice(0, result.boundary.end) + result.boundary.closers;
1080
+ return tryParse(candidate).ok ? candidate : void 0;
1081
+ };
1082
+ var isTruncatedJson = (content) => {
1083
+ const cleaned = content.replace(INVISIBLES, "").trim();
1084
+ if (!cleaned.startsWith("{") && !cleaned.startsWith("[")) return false;
1085
+ if (tryParse(cleaned).ok) return false;
1086
+ return (scan(cleaned)?.openAtEnd ?? 0) > 0;
1087
+ };
1088
+
1089
+ // src/wire/strategies/failed-generation.ts
1090
+ var RECOVERABLE_CODES = /* @__PURE__ */ new Set(["tool_use_failed", "json_validate_failed"]);
1091
+ var prop = (obj, key) => obj !== null && typeof obj === "object" ? obj[key] : void 0;
1092
+ var unwrap = (error) => error instanceof SignalError ? error.context?.["raw"] ?? error : error;
1093
+ var errorCode = (error) => {
1094
+ const direct = prop(error, "code");
1095
+ if (typeof direct === "string") return direct;
1096
+ const nested = prop(prop(error, "error"), "code");
1097
+ return typeof nested === "string" ? nested : void 0;
1098
+ };
1099
+ var failedGenerationOf = (error) => {
1100
+ const gen = prop(prop(error, "error"), "failed_generation");
1101
+ return typeof gen === "string" && gen.trim().length > 0 ? gen : void 0;
1102
+ };
1103
+ var asCallShape = (value) => {
1104
+ const name = prop(value, "name");
1105
+ if (typeof name !== "string" || name.length === 0) return void 0;
1106
+ const args = prop(value, "arguments");
1107
+ if (args === void 0) return void 0;
1108
+ if (typeof args === "string") return { name, argsText: args };
1109
+ return { name, args, argsText: JSON.stringify(args) };
1110
+ };
1111
+ var recover = (error) => {
1112
+ const raw = failedGenerationOf(unwrap(error));
1113
+ if (raw === void 0) return { argsText: "", truncated: false };
1114
+ try {
1115
+ const parsed = JSON.parse(raw);
1116
+ const call = asCallShape(parsed);
1117
+ if (call) return { name: call.name, ...call.args !== void 0 && { args: call.args }, argsText: call.argsText, truncated: false };
1118
+ return { args: parsed, argsText: raw, truncated: false };
1119
+ } catch {
1120
+ const truncated = isTruncatedJson(raw);
1121
+ const closed = closeTruncated(raw);
1122
+ if (closed !== void 0) {
1123
+ try {
1124
+ const call = asCallShape(JSON.parse(closed));
1125
+ if (call) return { name: call.name, ...call.args !== void 0 && { args: call.args }, argsText: call.argsText, truncated };
1126
+ } catch {
1127
+ }
1128
+ }
1129
+ return { argsText: raw, truncated };
1130
+ }
1131
+ };
1132
+ var failedGeneration = {
1133
+ id: "failed-generation",
1134
+ hook: "error",
1135
+ matches: (error) => {
1136
+ const code = errorCode(unwrap(error));
1137
+ return code !== void 0 && RECOVERABLE_CODES.has(code);
1138
+ },
1139
+ recover
1140
+ };
1141
+
1142
+ // src/wire/strategies/jsonl-lines.ts
1143
+ var jsonlLines = {
1144
+ id: "jsonl-lines",
1145
+ hook: "response",
1146
+ candidates: (content) => {
1147
+ const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.startsWith("{") || line.startsWith("["));
1148
+ return lines.length > 1 ? lines.reverse() : [];
1149
+ }
1150
+ };
1151
+
1152
+ // src/wire/strategies/index.ts
1153
+ var REGISTRY = {
1154
+ [failedGeneration.id]: failedGeneration,
1155
+ [jsonlLines.id]: jsonlLines
1156
+ };
1157
+ var resolveWireStrategies = (ids) => ids.map((id) => {
1158
+ const strategy = REGISTRY[id];
1159
+ if (!strategy) throw new Error(`Unknown wire strategy "${id}" \u2014 known: ${Object.keys(REGISTRY).join(", ")}`);
1160
+ return strategy;
1161
+ });
1162
+ var errorStrategies = (strategies) => strategies.filter((strategy) => strategy.hook === "error");
1163
+ var responseStrategies = (strategies) => strategies.filter((strategy) => strategy.hook === "response");
1164
+ var recoverRejection = (strategies, error) => {
1165
+ for (const strategy of errorStrategies(strategies)) {
1166
+ if (!strategy.matches(error)) continue;
1167
+ const rejection = strategy.recover(error);
1168
+ if (rejection) return { strategy: strategy.id, rejection };
1169
+ }
1170
+ return void 0;
1171
+ };
1172
+
1173
+ // src/wire/router.ts
1174
+ var toolTurn = (calls, notes) => ({
1175
+ outcome: { kind: "tool_calls", calls },
1176
+ wire: notes.length > 0 ? { notes } : {}
1177
+ });
1178
+ var output = (value, rung, note) => ({
1179
+ outcome: { kind: "output", value },
1180
+ wire: note === void 0 ? { rung } : { rung, notes: [note] }
1181
+ });
1182
+ var failed = (evidence, flags = {}) => ({
1183
+ outcome: {
1184
+ kind: "failed",
1185
+ evidence,
1186
+ ...flags.truncated === true && { truncated: true }
1187
+ },
1188
+ wire: {}
1189
+ });
1190
+ var tryParse2 = (text) => {
1191
+ try {
1192
+ return { ok: true, value: JSON.parse(text) };
1193
+ } catch (error) {
1194
+ return { ok: false, why: error instanceof Error ? error.message : String(error) };
1195
+ }
1196
+ };
1197
+ var whyNotJson = (text) => {
1198
+ const direct = tryParse2(text.trim());
1199
+ return direct.ok ? void 0 : direct.why;
1200
+ };
1201
+ function* ladder(text, strategies) {
1202
+ const trimmed = text.trim();
1203
+ if (trimmed.length === 0) return;
1204
+ const direct = tryParse2(trimmed);
1205
+ if (direct.ok) yield { value: direct.value, rung: "parse" };
1206
+ const extracted = extractJson(trimmed);
1207
+ if (extracted.ok) yield { value: extracted.value, rung: "extract" };
1208
+ for (const repaired of repairEscapeDamage(trimmed)) {
1209
+ const parsed = tryParse2(repaired);
1210
+ if (parsed.ok) yield { value: parsed.value, rung: "repair-escapes" };
1211
+ }
1212
+ for (const strategy of strategies) {
1213
+ for (const candidateText of strategy.candidates(trimmed)) {
1214
+ const parsed = tryParse2(candidateText);
1215
+ if (parsed.ok) yield { value: parsed.value, rung: strategy.id };
1216
+ }
1217
+ }
1218
+ const closed = closeTruncated(trimmed);
1219
+ if (closed !== void 0) {
1220
+ const parsed = tryParse2(closed);
1221
+ if (parsed.ok) yield { value: parsed.value, rung: "close-truncated" };
1222
+ }
1223
+ }
1224
+ var firstAccepted = (candidates, accept) => {
1225
+ let nearMiss;
1226
+ for (const candidate of candidates) {
1227
+ const direct = accept.safeParse(candidate.value);
1228
+ if (direct.success) return { winner: candidate };
1229
+ const deep = deepDecodeJsonish(candidate.value);
1230
+ if (deep !== candidate.value && accept.safeParse(deep).success) {
1231
+ return { winner: { value: deep, rung: `${candidate.rung}+deep-decode` } };
1232
+ }
1233
+ nearMiss ??= direct.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
1234
+ }
1235
+ return { ...nearMiss !== void 0 && { nearMiss } };
1236
+ };
1237
+ var decodedCall = (call) => ({ ...call, args: decodeJsonish(call.args) });
1238
+ var finishAttempt = (call, request) => {
1239
+ if (!request.accept) return void 0;
1240
+ const source = typeof call.args === "string" ? ladder(call.args, request.responseStrategies) : [{ value: call.args, rung: "call-args" }];
1241
+ const { winner, nearMiss } = firstAccepted(source, request.accept);
1242
+ if (winner) {
1243
+ const note = call.name === request.outputTool ? void 0 : `output arrived as a call to undeclared tool "${call.name}"`;
1244
+ return output(winner.value, winner.rung, note);
1245
+ }
1246
+ if (nearMiss !== void 0) {
1247
+ return failed(`the call to "${call.name}" carried an invalid output: ${nearMiss}`);
1248
+ }
1249
+ return void 0;
1250
+ };
1251
+ var contentEvidence = (content, nearMiss) => {
1252
+ if (nearMiss !== void 0) return nearMiss;
1253
+ const snippet = content.trim().slice(0, 160);
1254
+ if (snippet.length === 0) return "the turn produced no output";
1255
+ const why = whyNotJson(content);
1256
+ return why === void 0 ? `output is not valid JSON \u2014 it began: ${snippet}` : `output is not valid JSON: ${why}. It began: ${snippet}`;
1257
+ };
1258
+ var contentTurn = (request) => {
1259
+ if (!request.accept) return failed("no acceptance schema for a content turn");
1260
+ const { winner, nearMiss } = firstAccepted(ladder(request.content, request.responseStrategies), request.accept);
1261
+ if (winner) return output(winner.value, winner.rung);
1262
+ return failed(contentEvidence(request.content, nearMiss), {
1263
+ truncated: isTruncatedJson(request.content)
1264
+ });
1265
+ };
1266
+ var routeResponse = (request) => {
1267
+ const isExit = (call) => call.name === request.outputTool;
1268
+ const domainCalls = request.toolCalls.filter((call) => request.declared.has(call.name) && !isExit(call));
1269
+ const attempts = request.toolCalls.filter((call) => !request.declared.has(call.name) || isExit(call));
1270
+ if (domainCalls.length > 0) {
1271
+ const dropped = attempts.map((call) => `dropped call to "${call.name}" in a mixed turn \u2014 finish attempts must come alone`);
1272
+ return toolTurn(domainCalls.map(decodedCall), dropped);
1273
+ }
1274
+ for (const call of attempts) {
1275
+ const routed = finishAttempt(call, request);
1276
+ if (routed) return routed;
1277
+ }
1278
+ if (attempts.length > 0) {
1279
+ return failed(`called unknown tool(s) ${attempts.map((call) => `"${call.name}"`).join(", ")}`);
1280
+ }
1281
+ return contentTurn(request);
1282
+ };
1283
+ var recoveredCallCounter = 0;
1284
+ var routeRejection = (rejection, request) => {
1285
+ const name = rejection.name;
1286
+ if (name !== void 0 && request.declared.has(name) && name !== request.outputTool) {
1287
+ recoveredCallCounter += 1;
1288
+ const args = rejection.args ?? decodeJsonish(rejection.argsText);
1289
+ return toolTurn([{ id: `recovered_${recoveredCallCounter}`, name, args }], []);
1290
+ }
1291
+ const call = { name: name ?? "", args: rejection.args ?? rejection.argsText };
1292
+ const routed = finishAttempt(call, { ...request});
1293
+ if (routed) {
1294
+ if (routed.outcome.kind === "failed" && rejection.truncated) {
1295
+ return { ...routed, outcome: { ...routed.outcome, truncated: true } };
1296
+ }
1297
+ return routed;
1298
+ }
1299
+ return failed(
1300
+ rejection.argsText.length > 0 ? `provider rejected the attempt${name !== void 0 ? ` (call to unknown tool "${name}")` : ""} and nothing salvageable remained` : "provider rejected the attempt; nothing was recovered",
1301
+ { truncated: rejection.truncated }
1302
+ );
1303
+ };
1304
+
1305
+ // src/signal.ts
1306
+ var resolveProvider = (config) => {
1307
+ if (typeof config.provider === "string") {
1308
+ const entry = providerRegistry[config.provider];
1309
+ if (!entry) throw new SignalError(`Unknown provider: ${config.provider}`, ErrorCode.PROVIDER_NOT_FOUND);
1310
+ const apiKey2 = resolveApiKey(entry.envKey, config.apiKey);
1311
+ if (!apiKey2) throw new SignalError(`Missing API key for ${config.provider}. Set ${entry.envKey} or pass apiKey.`, ErrorCode.MISSING_API_KEY);
1312
+ return {
1313
+ model: config.model ?? entry.defaultModel,
1314
+ apiKey: apiKey2,
1315
+ baseUrl: entry.baseUrl,
1316
+ adapterType: entry.adapter,
1317
+ ...entry.adapter !== "systemone" && entry.reasoningRequest !== void 0 && { reasoningRequest: entry.reasoningRequest }
1318
+ };
1319
+ }
1320
+ const custom = config.provider;
1321
+ const apiKey = custom.apiKey ?? config.apiKey;
1322
+ if (!apiKey) throw new SignalError("Missing API key for custom provider", ErrorCode.MISSING_API_KEY);
1323
+ const model = config.model ?? custom.model;
1324
+ if (!model) throw new SignalError("Missing model for custom provider", ErrorCode.MISSING_MODEL);
1325
+ return { model, apiKey, baseUrl: custom.baseUrl, adapterType: custom.adapter ?? "openai-compatible" };
1326
+ };
1327
+ var FALLBACK_CAPABILITIES = {
1328
+ nativeTools: false,
1329
+ nativeJsonSchema: false,
1330
+ nativeJsonMode: false,
1331
+ toolsWithStructuredOutput: false,
1332
+ validatesToolArgs: false,
1333
+ manglesNestedToolArgs: false,
1334
+ multimodal: false,
1335
+ supportsEmbedding: false
1336
+ };
1337
+ var adapterTypeOf = (config) => typeof config.provider === "string" ? providerRegistry[config.provider]?.adapter : config.provider.adapter ?? "openai-compatible";
1338
+ var kindOf = (adapterType) => adapterType === "systemone" ? "decisions" : "chat";
1339
+ var modelRowOf = (config) => {
1340
+ if (typeof config.provider !== "string") return void 0;
1341
+ const entry = providerRegistry[config.provider];
1342
+ if (entry === void 0) return void 0;
1343
+ const model = config.model ?? entry.defaultModel;
1344
+ return { provider: config.provider, model, row: modelEntry(config.provider, model) };
1345
+ };
1346
+ var resolveCapabilities = (config) => {
1347
+ if (typeof config.provider === "string") {
1348
+ const entry = providerRegistry[config.provider];
1349
+ if (entry === void 0 || entry.adapter === "systemone") return FALLBACK_CAPABILITIES;
1350
+ return { ...entry.endpoint, ...modelRowOf(config)?.row?.capabilities ?? UNMEASURED_MODEL };
1351
+ }
1352
+ const declared = config.provider.adapter === "systemone" ? {} : config.provider.capabilities;
1353
+ return { ...FALLBACK_CAPABILITIES, ...declared };
1354
+ };
1355
+ var assertEffort = (config, effort) => {
1356
+ if (effort === void 0) return;
1357
+ const resolved = modelRowOf(config);
1358
+ const row = resolved?.row;
1359
+ if (resolved === void 0 || row === void 0 || row.reasoningEfforts.includes(effort)) return;
1360
+ const accepted = row.reasoningEfforts.length === 0 ? "nothing \u2014 it takes no reasoning effort" : row.reasoningEfforts.join(", ");
1361
+ throw new SignalError(
1362
+ `reasoningEffort "${effort}" is not accepted by ${resolved.provider} ${resolved.model} (accepts: ${accepted}; measured ${row.verified})`,
1363
+ ErrorCode.VALIDATION_FAILED
1364
+ );
1365
+ };
1366
+ var wireIdsOf = (config) => {
1367
+ const source = typeof config.provider === "string" ? providerRegistry[config.provider] : config.provider;
1368
+ return source === void 0 || source.adapter === "systemone" ? [] : source.wire ?? [];
1369
+ };
1370
+ var createAdapter = async (resolved, client) => {
1371
+ switch (resolved.adapterType) {
1372
+ case "openai-compatible":
1373
+ return createOpenAICompatibleAdapter({ apiKey: resolved.apiKey, baseUrl: resolved.baseUrl, client, ...resolved.reasoningRequest !== void 0 && { reasoningRequest: resolved.reasoningRequest } });
1374
+ case "anthropic":
1375
+ return createAnthropicAdapter({ apiKey: resolved.apiKey});
1376
+ case "google":
1377
+ return createGoogleAdapter({ apiKey: resolved.apiKey});
1378
+ case "systemone":
1379
+ return createSystemOneAdapter({ apiKey: resolved.apiKey, baseUrl: resolved.baseUrl, client });
1380
+ }
1381
+ };
1382
+ var buildMessages = (config, input) => {
1383
+ const messages = [];
1384
+ if (config.systemPrompt) messages.push({ role: "system", content: config.systemPrompt });
1385
+ if (config.history) messages.push(...config.history);
1386
+ messages.push({ role: "user", content: input });
1387
+ return messages;
1388
+ };
1389
+ var recoveredStepResult = (recovered, routed, raw, sent) => ({
1390
+ content: "",
1391
+ toolCalls: routed.outcome.kind === "tool_calls" ? routed.outcome.calls : [],
1392
+ // A rejected generation costs tokens — the provider processed the request and
1393
+ // billed for it — and the 400 body carries none. The prompt is known exactly
1394
+ // and the model's attempt is in the rejection, so this is countable rather
1395
+ // than lost. Marked unreported, because it is a reckoning.
1396
+ usage: estimateUsage(sent, recovered.rejection.argsText, routed.outcome.kind === "tool_calls" ? routed.outcome.calls : []),
1397
+ finishReason: "error_recovered",
1398
+ raw,
1399
+ outcome: routed.outcome,
1400
+ wire: {
1401
+ ...routed.wire,
1402
+ recovered: {
1403
+ strategy: recovered.strategy,
1404
+ ...recovered.rejection.name !== void 0 && { name: recovered.rejection.name },
1405
+ truncated: recovered.rejection.truncated
1406
+ }
1407
+ }
1408
+ });
1409
+ var createSignalFromConfig = (config) => {
1410
+ let cachedAdapter;
1411
+ let cachedAdapterKey;
1412
+ const wireStrategies = resolveWireStrategies(wireIdsOf(config));
1413
+ assertEffort(config, config.options?.reasoningEffort);
1414
+ const getAdapter = async () => {
1415
+ const resolved = resolveProvider(config);
1416
+ const key = `${resolved.adapterType}:${resolved.baseUrl}:${resolved.apiKey}`;
1417
+ if (cachedAdapter && cachedAdapterKey === key) return cachedAdapter;
1418
+ cachedAdapter = await createAdapter(resolved, config.client);
1419
+ cachedAdapterKey = key;
1420
+ return cachedAdapter;
1421
+ };
1422
+ const getChatAdapter = async (verb) => {
1423
+ const adapter = await getAdapter();
1424
+ if (adapter.kind === "chat") return adapter;
1425
+ throw new SignalError(
1426
+ `${verb}() is not available on a decision provider \u2014 it answers decide() and generates no text`,
1427
+ ErrorCode.VERB_NOT_SUPPORTED,
1428
+ { verb, adapter: adapter.id }
1429
+ );
1430
+ };
1431
+ const fork = (override) => createSignalFromConfig({ ...config, ...override });
1432
+ const step = async (request) => {
1433
+ const adapter = await getChatAdapter("step");
1434
+ const resolved = resolveProvider(config);
1435
+ const messages = request.messages.slice();
1436
+ const providerTools = request.tools && request.tools.length > 0 ? request.tools.map((tool) => ({
1437
+ type: "function",
1438
+ function: {
1439
+ name: tool.name,
1440
+ description: tool.description,
1441
+ parameters: tool.parameters
1442
+ }
1443
+ })) : void 0;
1444
+ const providerRequest = {
1445
+ model: resolved.model,
1446
+ messages,
1447
+ ...providerTools && { tools: providerTools },
1448
+ ...request.toolChoice !== void 0 && { toolChoice: request.toolChoice },
1449
+ ...request.responseFormat !== void 0 && { responseFormat: request.responseFormat },
1450
+ // The client's own options are the floor; a request may override them
1451
+ // field by field. `complete`/`stream` already run on config.options —
1452
+ // step used to drop them, so a configured client behaved differently
1453
+ // depending on which door the caller came through.
1454
+ ...(config.options !== void 0 || request.options !== void 0) && {
1455
+ options: { ...config.options, ...request.options }
1456
+ }
1457
+ };
1458
+ assertEffort(config, providerRequest.options?.reasoningEffort);
1459
+ const declared = new Set((request.tools ?? []).map((tool) => tool.name));
1460
+ let response;
1461
+ try {
1462
+ response = await adapter.chat(providerRequest);
1463
+ } catch (error) {
1464
+ const recovered = recoverRejection(wireStrategies, error);
1465
+ if (!recovered) throw error;
1466
+ const routed2 = routeRejection(recovered.rejection, {
1467
+ declared,
1468
+ ...request.output && { accept: request.output.accept },
1469
+ ...request.output?.outputTool !== void 0 && { outputTool: request.output.outputTool },
1470
+ responseStrategies: responseStrategies(wireStrategies)
1471
+ });
1472
+ return recoveredStepResult(recovered, routed2, error, providerRequest.messages);
1473
+ }
1474
+ const toolCalls = (response.toolCalls ?? []).map((call) => {
1475
+ let parsed = call.args;
1476
+ if (typeof call.args === "string") {
1477
+ try {
1478
+ parsed = JSON.parse(call.args);
1479
+ } catch {
1480
+ parsed = call.args;
1481
+ }
1482
+ }
1483
+ return { id: call.id, name: call.name, args: parsed };
1484
+ });
1485
+ const result = {
1486
+ content: response.content,
1487
+ toolCalls,
1488
+ // The non-streaming body always carries usage, so a zero here is a real
1489
+ // zero rather than a missing frame.
1490
+ usage: { ...response.usage, reported: true },
1491
+ finishReason: response.finishReason,
1492
+ raw: response.raw
1493
+ };
1494
+ if (!request.output) return result;
1495
+ const routed = routeResponse({
1496
+ content: result.content,
1497
+ toolCalls: result.toolCalls,
1498
+ declared,
1499
+ accept: request.output.accept,
1500
+ ...request.output.outputTool !== void 0 && { outputTool: request.output.outputTool },
1501
+ responseStrategies: responseStrategies(wireStrategies)
1502
+ });
1503
+ return { ...result, outcome: routed.outcome, wire: routed.wire };
1504
+ };
1505
+ const stepStream = (request, streamOptions) => {
1506
+ const run = async function* () {
1507
+ const adapter = await getChatAdapter("stepStream");
1508
+ const resolved = resolveProvider(config);
1509
+ const declared = new Set((request.tools ?? []).map((tool) => tool.name));
1510
+ const merged = config.options !== void 0 || request.options !== void 0 ? { ...request, options: { ...config.options, ...request.options } } : request;
1511
+ assertEffort(config, merged.options?.reasoningEffort);
1512
+ try {
1513
+ for await (const event of executeStepStream({
1514
+ adapter,
1515
+ model: resolved.model,
1516
+ request: merged,
1517
+ ...streamOptions && { streamOptions }
1518
+ })) {
1519
+ if (event.type === "done" && request.output) {
1520
+ const routed = routeResponse({
1521
+ content: event.result.content,
1522
+ toolCalls: event.result.toolCalls,
1523
+ declared,
1524
+ accept: request.output.accept,
1525
+ ...request.output.outputTool !== void 0 && { outputTool: request.output.outputTool },
1526
+ responseStrategies: responseStrategies(wireStrategies)
1527
+ });
1528
+ yield { type: "done", result: { ...event.result, outcome: routed.outcome, wire: routed.wire } };
1529
+ continue;
1530
+ }
1531
+ yield event;
1532
+ }
1533
+ } catch (error) {
1534
+ const recovered = recoverRejection(wireStrategies, error);
1535
+ if (!recovered) throw error;
1536
+ const routed = routeRejection(recovered.rejection, {
1537
+ declared,
1538
+ ...request.output && { accept: request.output.accept },
1539
+ ...request.output?.outputTool !== void 0 && { outputTool: request.output.outputTool },
1540
+ responseStrategies: responseStrategies(wireStrategies)
1541
+ });
1542
+ yield { type: "done", result: recoveredStepResult(recovered, routed, error, request.messages) };
1543
+ }
1544
+ };
1545
+ return run();
1546
+ };
1547
+ const decideCalibrated = async (adapter, model, request, started) => {
1548
+ const response = await adapter.decide(
1549
+ { model, state: request.state, questions: request.questions },
1550
+ request.options?.signal !== void 0 ? { signal: request.options.signal } : void 0
1551
+ );
1552
+ const decisions = acceptCalibrated(request.questions, response.answers, response.raw);
1553
+ const inputTokens = response.usage?.inputTokens ?? 0;
1554
+ const outputTokens = response.usage?.outputTokens ?? 0;
1555
+ return {
1556
+ calibrated: true,
1557
+ decisions,
1558
+ meta: {
1559
+ model: response.model ?? model,
1560
+ usage: { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens, reported: response.usage !== void 0 },
1561
+ durationMs: Date.now() - started,
1562
+ provider: { raw: response.raw }
1563
+ }
1564
+ };
1565
+ };
1566
+ const decideEmulated = async (model, request, started) => {
1567
+ const state = typeof request.state === "string" ? request.state : JSON.stringify(request.state);
1568
+ const result = await runComplete(
1569
+ {
1570
+ messages: [{ role: "user", content: decisionEmulationPrompt(state) }],
1571
+ schema: uncalibratedSchemaOf(request.questions),
1572
+ tools: void 0,
1573
+ retries: config.retries ?? 2,
1574
+ options: config.options,
1575
+ ...request.options?.signal !== void 0 && { streamOptions: { signal: request.options.signal } }
1576
+ },
1577
+ { stepStream, model, capabilities: resolveCapabilities(config) }
1578
+ );
1579
+ return {
1580
+ calibrated: false,
1581
+ decisions: acceptUncalibrated(request.questions, result.response, result.meta.provider.raw),
1582
+ meta: {
1583
+ model: result.meta.model,
1584
+ // complete() sums what providers reported with what it had to estimate
1585
+ // and does not say which, so this total cannot be vouched for as measured.
1586
+ usage: { ...result.meta.usage, reported: false },
1587
+ durationMs: Date.now() - started,
1588
+ provider: { raw: result.meta.provider.raw }
1589
+ }
1590
+ };
1591
+ };
1592
+ return {
1593
+ // Builder methods
1594
+ apiKey: (key) => fork({ apiKey: key }),
1595
+ model: (m) => fork({ model: m }),
1596
+ systemPrompt: (prompt) => fork({ systemPrompt: prompt }),
1597
+ history: (msgs) => fork({ history: msgs }),
1598
+ schema: (s) => fork({ schema: s }),
1599
+ tools: (t) => fork({ tools: t }),
1600
+ retries: (count) => fork({ retries: count }),
1601
+ options: (opts) => fork({ options: { ...config.options, ...opts } }),
1602
+ onRetry: (handler) => fork({ onRetry: handler }),
1603
+ onToolCall: (handler) => fork({ onToolCall: handler }),
1604
+ describe: () => {
1605
+ if (typeof config.provider === "string") {
1606
+ const entry = providerRegistry[config.provider];
1607
+ return {
1608
+ provider: config.provider,
1609
+ model: config.model ?? entry?.defaultModel,
1610
+ kind: kindOf(adapterTypeOf(config)),
1611
+ capabilities: resolveCapabilities(config),
1612
+ modelKnown: modelRowOf(config)?.row !== void 0
1613
+ };
1614
+ }
1615
+ return {
1616
+ provider: config.provider.baseUrl,
1617
+ model: config.model ?? config.provider.model,
1618
+ kind: kindOf(adapterTypeOf(config)),
1619
+ capabilities: resolveCapabilities(config),
1620
+ modelKnown: false
1621
+ };
1622
+ },
1623
+ // Execution — thin wrappers over the step core (src/complete.ts).
1624
+ complete: async (input) => runComplete(
1625
+ {
1626
+ messages: buildMessages(config, input),
1627
+ schema: config.schema,
1628
+ tools: config.tools,
1629
+ retries: config.retries ?? 2,
1630
+ options: config.options,
1631
+ onRetry: config.onRetry,
1632
+ onToolCall: config.onToolCall
1633
+ },
1634
+ { stepStream, model: resolveProvider(config).model, capabilities: resolveCapabilities(config) }
1635
+ ),
1636
+ stream: (input, streamOptions) => runStream(
1637
+ {
1638
+ messages: buildMessages(config, input),
1639
+ schema: config.schema,
1640
+ tools: config.tools,
1641
+ retries: config.retries ?? 2,
1642
+ options: config.options,
1643
+ streamOptions,
1644
+ onRetry: config.onRetry,
1645
+ onToolCall: config.onToolCall
1646
+ },
1647
+ { stepStream, model: resolveProvider(config).model, capabilities: resolveCapabilities(config) }
1648
+ ),
1649
+ // ─── Low-level primitives — the execution core itself ────
1650
+ step,
1651
+ stepStream,
1652
+ // ─── Embedding ────────────────────────────────────────────
1653
+ embed: (async (input, options) => {
1654
+ const adapter = await getChatAdapter("embed");
1655
+ if (!adapter.embed) {
1656
+ throw new SignalError("This provider does not support embedding", ErrorCode.PROVIDER_ERROR);
1657
+ }
1658
+ const resolved = resolveProvider(config);
1659
+ const response = await adapter.embed({
1660
+ model: resolved.model,
1661
+ input,
1662
+ dimensions: options?.dimensions
1663
+ });
1664
+ options?.onUsage?.(response.usage, resolved.model);
1665
+ if (typeof input === "string") {
1666
+ const vector = response.embeddings[0];
1667
+ if (!vector) throw new SignalError("No embedding returned", ErrorCode.PROVIDER_ERROR);
1668
+ return vector;
1669
+ }
1670
+ return response.embeddings;
1671
+ }),
1672
+ // ─── Decisions ────────────────────────────────────────────
1673
+ decide: async (request) => {
1674
+ assertQuestions(request.questions);
1675
+ const started = Date.now();
1676
+ const adapter = await getAdapter();
1677
+ const model = resolveProvider(config).model;
1678
+ return adapter.kind === "decisions" ? decideCalibrated(adapter, model, request, started) : decideEmulated(model, request, started);
1679
+ },
1680
+ // ─── Low-level: token counting (heuristic) ───────────────
1681
+ count: async (input) => {
1682
+ const CHARS_PER_TOKEN2 = 4;
1683
+ if (typeof input === "string") {
1684
+ return Math.ceil(input.length / CHARS_PER_TOKEN2);
1685
+ }
1686
+ let total = 0;
1687
+ for (const msg of input) {
1688
+ total += 4;
1689
+ if (typeof msg.content === "string") {
1690
+ total += Math.ceil(msg.content.length / CHARS_PER_TOKEN2);
1691
+ continue;
1692
+ }
1693
+ for (const part of msg.content) {
1694
+ if (part.type === "text") {
1695
+ total += Math.ceil(part.text.length / CHARS_PER_TOKEN2);
1696
+ } else {
1697
+ total += 256;
1698
+ }
1699
+ }
1700
+ }
1701
+ return total;
1702
+ }
1703
+ };
1704
+ };
1705
+ var createSignal = (provider, options) => createSignalFromConfig({ provider, ...options });
1706
+
1707
+ // src/tools/define-tool.ts
1708
+ var defineTool = (config) => ({
1709
+ name: config.name,
1710
+ description: config.description,
1711
+ inputSchema: config.input,
1712
+ execute: config.execute
1713
+ });
1714
+
1715
+ // src/transport/resolve.ts
1716
+ var RESPOND_TOOL_NAME = "respond";
1717
+ var FULL_PARAMS_MAX_CHARS = 8192;
1718
+ var analyzeWireSchema = (schema) => {
1719
+ try {
1720
+ const serialized = schema["~standard"].jsonSchema.output({ target: "draft-07" });
1721
+ const text = JSON.stringify(serialized);
1722
+ return { serialized, hasRef: text.includes('"$ref"'), size: text.length };
1723
+ } catch {
1724
+ return { serialized: null, hasRef: false, size: Number.POSITIVE_INFINITY };
1725
+ }
1726
+ };
1727
+ var toJsonSchemaRecord = (schema) => {
1728
+ const convert = schema["~standard"].jsonSchema.output;
1729
+ try {
1730
+ return convert({ target: "draft-07" });
1731
+ } catch {
1732
+ return convert({ target: "draft-07", libraryOptions: { unrepresentable: "any" } });
1733
+ }
1734
+ };
1735
+ var respondParams = (spec, caps, analysis, fullIsViable) => {
1736
+ if (caps.validatesToolArgs) {
1737
+ return {
1738
+ detail: "permissive",
1739
+ parameters: {
1740
+ type: "object",
1741
+ properties: spec.hasData ? { response: {}, data: {}, reasoning: {} } : { response: {}, reasoning: {} }
1742
+ }
1743
+ };
1744
+ }
1745
+ if (!spec.hasData || fullIsViable) {
1746
+ return { detail: "full", parameters: analysis.serialized ?? toJsonSchemaRecord(spec.wire) };
1747
+ }
1748
+ return { detail: "loose", parameters: toJsonSchemaRecord(spec.looseWire) };
1749
+ };
1750
+ var protocolSpec = (transport, spec, injectSchemaDoc) => ({
1751
+ transport,
1752
+ responseMode: spec.responseMode,
1753
+ hasData: spec.hasData,
1754
+ hasTools: spec.hasTools,
1755
+ injectSchemaDoc
1756
+ });
1757
+ var resolveTransport = (spec, caps) => {
1758
+ const analysis = analyzeWireSchema(spec.wire);
1759
+ const fullIsViable = analysis.serialized !== null && !analysis.hasRef && analysis.size <= FULL_PARAMS_MAX_CHARS;
1760
+ const nativeIsViable = caps.nativeJsonSchema && fullIsViable && (!spec.hasTools || caps.toolsWithStructuredOutput);
1761
+ const choice = spec.choice ?? "auto";
1762
+ if (choice === "native" && !nativeIsViable) {
1763
+ throw new SignalError(
1764
+ spec.hasTools && !caps.toolsWithStructuredOutput ? 'transport "native" needs a provider that combines tools with response_format (capabilities.toolsWithStructuredOutput)' : 'transport "native" needs capabilities.nativeJsonSchema and a wire schema that serializes small and non-recursive',
1765
+ ErrorCode.VALIDATION_FAILED
1766
+ );
1767
+ }
1768
+ const respondEnforces = !caps.validatesToolArgs && !caps.manglesNestedToolArgs && (!spec.hasData || fullIsViable);
1769
+ let transport;
1770
+ if (choice !== "auto") transport = choice;
1771
+ else if (nativeIsViable && spec.hasData) transport = "native";
1772
+ else if (respondEnforces || spec.forceTool) transport = "respond";
1773
+ else transport = "emit";
1774
+ if (transport === "emit" && spec.forceTool) {
1775
+ throw new SignalError(
1776
+ "output.forceTool cannot combine with the emit transport \u2014 the final turn must be a content-only message",
1777
+ ErrorCode.VALIDATION_FAILED
1778
+ );
1779
+ }
1780
+ if (transport === "native") {
1781
+ const serialized = analysis.serialized ?? toJsonSchemaRecord(spec.wire);
1782
+ const injectSchemaDoc2 = false;
1783
+ return {
1784
+ transport,
1785
+ responseMode: spec.responseMode,
1786
+ responseFormat: {
1787
+ type: "json_schema",
1788
+ // strict:false — strict mode rejects open records and several
1789
+ // keywords; non-strict still constrains and never hard-fails.
1790
+ jsonSchema: { name: "envelope", strict: false, schema: serialized }
1791
+ },
1792
+ injectSchemaDoc: injectSchemaDoc2,
1793
+ finishProtocol: finishProtocol(protocolSpec(transport, spec, injectSchemaDoc2)),
1794
+ corrections: corrections(transport)
1795
+ };
1796
+ }
1797
+ if (transport === "emit") {
1798
+ const injectSchemaDoc2 = spec.hasData;
1799
+ return {
1800
+ transport,
1801
+ responseMode: spec.responseMode,
1802
+ injectSchemaDoc: injectSchemaDoc2,
1803
+ finishProtocol: finishProtocol(protocolSpec(transport, spec, injectSchemaDoc2)),
1804
+ corrections: corrections(transport)
1805
+ };
1806
+ }
1807
+ const { detail: respondDetail, parameters } = respondParams(spec, caps, analysis, fullIsViable);
1808
+ const injectSchemaDoc = spec.hasData && respondDetail !== "full";
1809
+ return {
1810
+ transport,
1811
+ responseMode: spec.responseMode,
1812
+ respondDetail,
1813
+ respondDescriptor: { name: RESPOND_TOOL_NAME, description: RESPOND_DESCRIPTION, parameters },
1814
+ outputToolName: RESPOND_TOOL_NAME,
1815
+ ...spec.forceTool && { toolChoice: "required" },
1816
+ injectSchemaDoc,
1817
+ finishProtocol: finishProtocol(protocolSpec(transport, spec, injectSchemaDoc)),
1818
+ corrections: corrections(transport)
1819
+ };
1820
+ };
1821
+
1822
+ // src/types.ts
1823
+ var REASONING_EFFORTS = ["none", "default", "minimal", "low", "medium", "high", "xhigh", "max"];
1824
+
1825
+ exports.ErrorCode = ErrorCode;
1826
+ exports.REASONING_EFFORTS = REASONING_EFFORTS;
1827
+ exports.RESPOND_TOOL_NAME = RESPOND_TOOL_NAME;
1828
+ exports.SignalError = SignalError;
1829
+ exports.UNMEASURED_MODEL = UNMEASURED_MODEL;
1830
+ exports.chatProviderEntry = chatProviderEntry;
1831
+ exports.closeTruncated = closeTruncated;
1832
+ exports.createSignal = createSignal;
1833
+ exports.decodeJsonish = decodeJsonish;
1834
+ exports.deepDecodeJsonish = deepDecodeJsonish;
1835
+ exports.defineTool = defineTool;
1836
+ exports.extractJson = extractJson;
1837
+ exports.isTruncatedJson = isTruncatedJson;
1838
+ exports.modelEntry = modelEntry;
1839
+ exports.modelKey = modelKey;
1840
+ exports.modelRegistry = modelRegistry;
1841
+ exports.providerRegistry = providerRegistry;
1842
+ exports.repairEscapeDamage = repairEscapeDamage;
1843
+ exports.resolveTransport = resolveTransport;
1844
+ exports.routeRejection = routeRejection;
1845
+ exports.routeResponse = routeResponse;
1846
+ //# sourceMappingURL=index.cjs.map
1847
+ //# sourceMappingURL=index.cjs.map