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