@warmdrift/kgauto-compiler 2.0.0-alpha.33 → 2.0.0-alpha.34

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.
@@ -1,6 +1,6 @@
1
- import { G as GlassboxEvent } from '../types-2QzI0Ep1.mjs';
2
- export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-2QzI0Ep1.mjs';
3
- import '../ir-DubC20e3.mjs';
1
+ import { G as GlassboxEvent } from '../types-BoqPXoUb.mjs';
2
+ export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-BoqPXoUb.mjs';
3
+ import '../ir-BAAHLfFL.mjs';
4
4
  import '../dialect.mjs';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
- import { G as GlassboxEvent } from '../types-DYcO82xh.js';
2
- export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-DYcO82xh.js';
3
- import '../ir-GnYPkpGo.js';
1
+ import { G as GlassboxEvent } from '../types-CXyKj6-K.js';
2
+ export { A as AdvisoryFiredData, C as CompileDoneData, a as CompileStartData, E as ExecuteAttemptData, b as ExecuteSuccessData, F as FallbackWalkedData, c as GLASSBOX_STREAM_TTL_MS, d as GlassboxEventKind, e as GlassboxPubSub } from '../types-CXyKj6-K.js';
3
+ import '../ir-B_ygNURd.js';
4
4
  import '../dialect.js';
5
5
 
6
6
  /**
@@ -1,5 +1,5 @@
1
- import { G as GlassboxEvent } from '../types-2QzI0Ep1.mjs';
2
- import { i as Adapter, v as SectionKind } from '../ir-DubC20e3.mjs';
1
+ import { G as GlassboxEvent } from '../types-BoqPXoUb.mjs';
2
+ import { i as Adapter, v as SectionKind } from '../ir-BAAHLfFL.mjs';
3
3
  import '../dialect.mjs';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { G as GlassboxEvent } from '../types-DYcO82xh.js';
2
- import { i as Adapter, v as SectionKind } from '../ir-GnYPkpGo.js';
1
+ import { G as GlassboxEvent } from '../types-CXyKj6-K.js';
2
+ import { i as Adapter, v as SectionKind } from '../ir-B_ygNURd.js';
3
3
  import '../dialect.js';
4
4
 
5
5
  /**
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, S as SystemModelMessage, e as CompileResult, f as SectionRewrite, R as RecordInput, g as RecordOutcomeInput, O as OutcomeResult, h as OracleScore, B as BestPracticeAdvisory, i as Adapter, j as PerAxisMetrics, k as Provider, l as ChainEntry, G as Grounding } from './ir-DubC20e3.mjs';
2
- export { m as CallAttempt, n as CallError, o as ChainWithGrounding, p as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, q as MutationApplied, r as NormalizedTokens, s as OutcomeKind, t as PerAxisMetricsByModel, u as PromptSection, v as SectionKind, T as ToolCall, w as ToolDefinition } from './ir-DubC20e3.mjs';
1
+ import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, S as SystemModelMessage, e as CompileResult, f as SectionRewrite, R as RecordInput, g as RecordOutcomeInput, O as OutcomeResult, h as OracleScore, B as BestPracticeAdvisory, i as Adapter, j as PerAxisMetrics, k as Provider, l as ChainEntry, G as Grounding } from './ir-BAAHLfFL.mjs';
2
+ export { m as CallAttempt, n as CallError, o as ChainWithGrounding, p as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, q as MutationApplied, r as NormalizedTokens, s as OutcomeKind, t as PerAxisMetricsByModel, u as PromptSection, v as SectionKind, T as ToolCall, w as ToolDefinition } from './ir-BAAHLfFL.mjs';
3
3
  import { ModelProfile } from './profiles.mjs';
4
4
  export { ALIASES, CacheStrategy, CliffRule, LoweringSpec, RecoveryRule, StructuredOutputCapability, SystemPromptMode, allProfiles, getProfile, profilesByProvider, tryGetProfile } from './profiles.mjs';
5
5
  import { IntentArchetypeName } from './dialect.mjs';
@@ -54,6 +54,15 @@ interface ExecuteOptions {
54
54
  apiKeys?: ApiKeys;
55
55
  fetchImpl?: typeof fetch;
56
56
  providerOverrides?: ProviderOverrides;
57
+ /**
58
+ * alpha.34. When supplied AND the chosen provider supports streaming, the
59
+ * execute() per-provider branch switches to SSE wire format and invokes
60
+ * `onChunk(delta)` once per text-delta event. Single-shot behavior
61
+ * preserved when omitted (current consumers unaffected). Wired from
62
+ * `CallOptions.onChunk` by `call()` after gating on `profile.streaming`
63
+ * and `!opts.noStream`.
64
+ */
65
+ onChunk?: (chunk: string) => void;
57
66
  }
58
67
  interface ExecuteOk {
59
68
  ok: true;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, S as SystemModelMessage, e as CompileResult, f as SectionRewrite, R as RecordInput, g as RecordOutcomeInput, O as OutcomeResult, h as OracleScore, B as BestPracticeAdvisory, i as Adapter, j as PerAxisMetrics, k as Provider, l as ChainEntry, G as Grounding } from './ir-GnYPkpGo.js';
2
- export { m as CallAttempt, n as CallError, o as ChainWithGrounding, p as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, q as MutationApplied, r as NormalizedTokens, s as OutcomeKind, t as PerAxisMetricsByModel, u as PromptSection, v as SectionKind, T as ToolCall, w as ToolDefinition } from './ir-GnYPkpGo.js';
1
+ import { C as CompilePolicy, N as NormalizedResponse, A as ApiKeys, P as ProviderOverrides, a as CompiledRequest, b as PromptIR, c as CallOptions, d as CallResult, S as SystemModelMessage, e as CompileResult, f as SectionRewrite, R as RecordInput, g as RecordOutcomeInput, O as OutcomeResult, h as OracleScore, B as BestPracticeAdvisory, i as Adapter, j as PerAxisMetrics, k as Provider, l as ChainEntry, G as Grounding } from './ir-B_ygNURd.js';
2
+ export { m as CallAttempt, n as CallError, o as ChainWithGrounding, p as Constraints, F as FallbackReason, H as HistoryCachePolicy, I as IntentDeclaration, M as Message, q as MutationApplied, r as NormalizedTokens, s as OutcomeKind, t as PerAxisMetricsByModel, u as PromptSection, v as SectionKind, T as ToolCall, w as ToolDefinition } from './ir-B_ygNURd.js';
3
3
  import { ModelProfile } from './profiles.js';
4
4
  export { ALIASES, CacheStrategy, CliffRule, LoweringSpec, RecoveryRule, StructuredOutputCapability, SystemPromptMode, allProfiles, getProfile, profilesByProvider, tryGetProfile } from './profiles.js';
5
5
  import { IntentArchetypeName } from './dialect.js';
@@ -54,6 +54,15 @@ interface ExecuteOptions {
54
54
  apiKeys?: ApiKeys;
55
55
  fetchImpl?: typeof fetch;
56
56
  providerOverrides?: ProviderOverrides;
57
+ /**
58
+ * alpha.34. When supplied AND the chosen provider supports streaming, the
59
+ * execute() per-provider branch switches to SSE wire format and invokes
60
+ * `onChunk(delta)` once per text-delta event. Single-shot behavior
61
+ * preserved when omitted (current consumers unaffected). Wired from
62
+ * `CallOptions.onChunk` by `call()` after gating on `profile.streaming`
63
+ * and `!opts.noStream`.
64
+ */
65
+ onChunk?: (chunk: string) => void;
57
66
  }
58
67
  interface ExecuteOk {
59
68
  ok: true;
package/dist/index.js CHANGED
@@ -3361,8 +3361,295 @@ function getReachabilityDiagnostic(opts = {}) {
3361
3361
  return out;
3362
3362
  }
3363
3363
 
3364
- // src/execute.ts
3364
+ // src/streaming.ts
3365
3365
  var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
3366
+ async function streamAnthropic(request, apiKey, opts) {
3367
+ const { provider: _provider, ...body } = request;
3368
+ const fetchFn = opts.fetchImpl ?? fetch;
3369
+ let res;
3370
+ try {
3371
+ res = await fetchFn(ANTHROPIC_URL, {
3372
+ method: "POST",
3373
+ headers: {
3374
+ "x-api-key": apiKey,
3375
+ "anthropic-version": "2023-06-01",
3376
+ "content-type": "application/json"
3377
+ },
3378
+ body: JSON.stringify({ ...body, stream: true })
3379
+ });
3380
+ } catch (err) {
3381
+ return retryableError(0, "network_error", String(err), null);
3382
+ }
3383
+ if (!res.ok) {
3384
+ const errBody = await res.json().catch(() => ({}));
3385
+ return classifyHttpError(res.status, errBody);
3386
+ }
3387
+ let text = "";
3388
+ let inputTokens = 0;
3389
+ let outputTokens = 0;
3390
+ let cacheReadTokens;
3391
+ let cacheCreatedTokens;
3392
+ let stopReason;
3393
+ const toolBlocks = /* @__PURE__ */ new Map();
3394
+ try {
3395
+ await parseSSEStream(res, (event) => {
3396
+ if (!event.data) return;
3397
+ let payload;
3398
+ try {
3399
+ payload = JSON.parse(event.data);
3400
+ } catch {
3401
+ return;
3402
+ }
3403
+ const type = payload.type;
3404
+ if (type === "message_start") {
3405
+ const msg = payload.message;
3406
+ const usage = msg?.usage;
3407
+ if (usage) {
3408
+ inputTokens = usage.input_tokens ?? 0;
3409
+ outputTokens = usage.output_tokens ?? 0;
3410
+ if (typeof usage.cache_read_input_tokens === "number")
3411
+ cacheReadTokens = usage.cache_read_input_tokens;
3412
+ if (typeof usage.cache_creation_input_tokens === "number")
3413
+ cacheCreatedTokens = usage.cache_creation_input_tokens;
3414
+ }
3415
+ return;
3416
+ }
3417
+ if (type === "content_block_start") {
3418
+ const p = payload;
3419
+ const idx = p.index ?? 0;
3420
+ const block = p.content_block;
3421
+ if (block?.type === "tool_use" && block.id && block.name) {
3422
+ toolBlocks.set(idx, { id: block.id, name: block.name, argsJson: "" });
3423
+ }
3424
+ return;
3425
+ }
3426
+ if (type === "content_block_delta") {
3427
+ const p = payload;
3428
+ const delta = p.delta;
3429
+ if (!delta) return;
3430
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
3431
+ text += delta.text;
3432
+ opts.onChunk(delta.text);
3433
+ } else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
3434
+ const idx = p.index ?? 0;
3435
+ const tool = toolBlocks.get(idx);
3436
+ if (tool) tool.argsJson += delta.partial_json;
3437
+ }
3438
+ return;
3439
+ }
3440
+ if (type === "message_delta") {
3441
+ const p = payload;
3442
+ if (p.delta?.stop_reason) stopReason = p.delta.stop_reason;
3443
+ if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
3444
+ return;
3445
+ }
3446
+ });
3447
+ } catch (err) {
3448
+ return retryableError(0, "stream_interrupted", String(err), null);
3449
+ }
3450
+ const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
3451
+ id: b.id,
3452
+ name: b.name,
3453
+ args: tryParseJson(b.argsJson) ?? {}
3454
+ }));
3455
+ const tokens = {
3456
+ input: inputTokens,
3457
+ output: outputTokens,
3458
+ total: inputTokens + outputTokens,
3459
+ cached: cacheReadTokens,
3460
+ cacheCreated: cacheCreatedTokens
3461
+ };
3462
+ const response = {
3463
+ text,
3464
+ structuredOutput: null,
3465
+ toolCalls,
3466
+ tokens,
3467
+ finishReason: stopReason,
3468
+ raw: { streamed: true, provider: "anthropic" }
3469
+ };
3470
+ return { ok: true, status: res.status, response };
3471
+ }
3472
+ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
3473
+ const { provider: _provider, ...body } = request;
3474
+ const fetchFn = opts.fetchImpl ?? fetch;
3475
+ const reqBody = {
3476
+ ...body,
3477
+ stream: true,
3478
+ stream_options: {
3479
+ ...body.stream_options ?? {},
3480
+ include_usage: true
3481
+ }
3482
+ };
3483
+ let res;
3484
+ try {
3485
+ res = await fetchFn(url, {
3486
+ method: "POST",
3487
+ headers: {
3488
+ authorization: `Bearer ${apiKey}`,
3489
+ "content-type": "application/json"
3490
+ },
3491
+ body: JSON.stringify(reqBody)
3492
+ });
3493
+ } catch (err) {
3494
+ return retryableError(0, "network_error", String(err), null);
3495
+ }
3496
+ if (!res.ok) {
3497
+ const errBody = await res.json().catch(() => ({}));
3498
+ return classifyHttpError(res.status, errBody);
3499
+ }
3500
+ let text = "";
3501
+ let finishReason;
3502
+ let inputTokens = 0;
3503
+ let outputTokens = 0;
3504
+ let totalTokens;
3505
+ let cachedTokens;
3506
+ const toolBuffers = /* @__PURE__ */ new Map();
3507
+ try {
3508
+ await parseSSEStream(res, (event) => {
3509
+ if (!event.data) return;
3510
+ if (event.data === "[DONE]") return;
3511
+ let payload;
3512
+ try {
3513
+ payload = JSON.parse(event.data);
3514
+ } catch {
3515
+ return;
3516
+ }
3517
+ const choices = payload.choices;
3518
+ if (choices && choices.length > 0) {
3519
+ const choice = choices[0];
3520
+ const deltaContent = choice.delta?.content;
3521
+ if (typeof deltaContent === "string" && deltaContent.length > 0) {
3522
+ text += deltaContent;
3523
+ opts.onChunk(deltaContent);
3524
+ }
3525
+ const toolDeltas = choice.delta?.tool_calls;
3526
+ if (toolDeltas) {
3527
+ for (const tcDelta of toolDeltas) {
3528
+ const idx = tcDelta.index ?? 0;
3529
+ let buf = toolBuffers.get(idx);
3530
+ if (!buf) {
3531
+ buf = { id: tcDelta.id ?? `tc-${idx}`, name: "", argsJson: "" };
3532
+ toolBuffers.set(idx, buf);
3533
+ }
3534
+ if (tcDelta.id) buf.id = tcDelta.id;
3535
+ if (tcDelta.function?.name) buf.name += tcDelta.function.name;
3536
+ if (tcDelta.function?.arguments) buf.argsJson += tcDelta.function.arguments;
3537
+ }
3538
+ }
3539
+ if (choice.finish_reason) finishReason = choice.finish_reason;
3540
+ }
3541
+ const usage = payload.usage;
3542
+ if (usage) {
3543
+ if (typeof usage.prompt_tokens === "number") inputTokens = usage.prompt_tokens;
3544
+ if (typeof usage.completion_tokens === "number") outputTokens = usage.completion_tokens;
3545
+ if (typeof usage.total_tokens === "number") totalTokens = usage.total_tokens;
3546
+ const details = usage.prompt_tokens_details;
3547
+ if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
3548
+ }
3549
+ });
3550
+ } catch (err) {
3551
+ return retryableError(0, "stream_interrupted", String(err), null);
3552
+ }
3553
+ const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
3554
+ id: b.id,
3555
+ name: b.name,
3556
+ args: tryParseJson(b.argsJson) ?? {}
3557
+ }));
3558
+ const tokens = {
3559
+ input: inputTokens,
3560
+ output: outputTokens,
3561
+ total: totalTokens ?? inputTokens + outputTokens,
3562
+ cached: cachedTokens
3563
+ };
3564
+ const response = {
3565
+ text,
3566
+ structuredOutput: null,
3567
+ toolCalls,
3568
+ tokens,
3569
+ finishReason,
3570
+ raw: { streamed: true, provider: providerLabel }
3571
+ };
3572
+ return { ok: true, status: res.status, response };
3573
+ }
3574
+ async function parseSSEStream(response, handler) {
3575
+ const body = response.body;
3576
+ if (!body) throw new Error("Response has no body for SSE parse");
3577
+ const reader = body.getReader();
3578
+ const decoder = new TextDecoder("utf-8");
3579
+ let buffer = "";
3580
+ for (; ; ) {
3581
+ const { value, done } = await reader.read();
3582
+ if (done) break;
3583
+ buffer += decoder.decode(value, { stream: true });
3584
+ let sep;
3585
+ while (sep = buffer.indexOf("\n\n"), sep !== -1) {
3586
+ const block = buffer.slice(0, sep);
3587
+ buffer = buffer.slice(sep + 2);
3588
+ const event = parseSSEBlock(block);
3589
+ if (event) handler(event);
3590
+ }
3591
+ }
3592
+ if (buffer.length > 0) {
3593
+ const event = parseSSEBlock(buffer);
3594
+ if (event) handler(event);
3595
+ }
3596
+ }
3597
+ function parseSSEBlock(block) {
3598
+ const lines = block.split(/\r?\n/);
3599
+ let eventName;
3600
+ const dataLines = [];
3601
+ for (const line of lines) {
3602
+ if (line.startsWith(":")) continue;
3603
+ if (line.startsWith("event:")) {
3604
+ eventName = line.slice(6).trim();
3605
+ } else if (line.startsWith("data:")) {
3606
+ dataLines.push(line.slice(5).trim());
3607
+ }
3608
+ }
3609
+ if (dataLines.length === 0) return void 0;
3610
+ return { event: eventName, data: dataLines.join("\n") };
3611
+ }
3612
+ function tryParseJson(s) {
3613
+ if (typeof s !== "string" || s.length === 0) return void 0;
3614
+ try {
3615
+ const parsed = JSON.parse(s);
3616
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
3617
+ } catch {
3618
+ return void 0;
3619
+ }
3620
+ }
3621
+ function classifyHttpError(status, body) {
3622
+ const message = extractErrorMessage(body) ?? `HTTP ${status}`;
3623
+ if (status === 429)
3624
+ return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
3625
+ if (status === 408)
3626
+ return { ok: false, status, errorType: "retryable", errorCode: "timeout", message, raw: body };
3627
+ if (status >= 500)
3628
+ return { ok: false, status, errorType: "retryable", errorCode: "server_error", message, raw: body };
3629
+ if (status === 404)
3630
+ return { ok: false, status, errorType: "retryable", errorCode: "model_not_found", message, raw: body };
3631
+ if (status === 401 || status === 403)
3632
+ return { ok: false, status, errorType: "terminal", errorCode: "auth", message, raw: body };
3633
+ if (status === 400)
3634
+ return { ok: false, status, errorType: "terminal", errorCode: "invalid_request", message, raw: body };
3635
+ return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
3636
+ }
3637
+ function extractErrorMessage(body) {
3638
+ if (!body || typeof body !== "object") return void 0;
3639
+ const b = body;
3640
+ if (b.error && typeof b.error === "object") {
3641
+ const e = b.error;
3642
+ if (typeof e.message === "string") return e.message;
3643
+ }
3644
+ if (typeof b.message === "string") return b.message;
3645
+ return void 0;
3646
+ }
3647
+ function retryableError(status, code, message, raw) {
3648
+ return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
3649
+ }
3650
+
3651
+ // src/execute.ts
3652
+ var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
3366
3653
  var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
3367
3654
  var DEEPSEEK_URL = "https://api.deepseek.com/chat/completions";
3368
3655
  async function execute(request, opts = {}) {
@@ -3387,12 +3674,18 @@ async function executeAnthropic(request, opts) {
3387
3674
  if (!apiKey) {
3388
3675
  return terminalError(401, "auth", "ANTHROPIC_API_KEY missing");
3389
3676
  }
3677
+ if (opts.onChunk) {
3678
+ return streamAnthropic(request, apiKey, {
3679
+ onChunk: opts.onChunk,
3680
+ fetchImpl: opts.fetchImpl
3681
+ });
3682
+ }
3390
3683
  const { provider: _provider, ...body } = request;
3391
3684
  const fetchFn = opts.fetchImpl ?? fetch;
3392
3685
  let res;
3393
3686
  let json;
3394
3687
  try {
3395
- res = await fetchFn(ANTHROPIC_URL, {
3688
+ res = await fetchFn(ANTHROPIC_URL2, {
3396
3689
  method: "POST",
3397
3690
  headers: {
3398
3691
  "x-api-key": apiKey,
@@ -3403,9 +3696,9 @@ async function executeAnthropic(request, opts) {
3403
3696
  });
3404
3697
  json = await res.json().catch(() => ({}));
3405
3698
  } catch (err) {
3406
- return retryableError(0, "network_error", String(err), null);
3699
+ return retryableError2(0, "network_error", String(err), null);
3407
3700
  }
3408
- if (!res.ok) return classifyHttpError(res.status, json);
3701
+ if (!res.ok) return classifyHttpError2(res.status, json);
3409
3702
  return { ok: true, status: res.status, response: normalizeAnthropic(json) };
3410
3703
  }
3411
3704
  function normalizeAnthropic(raw) {
@@ -3439,9 +3732,9 @@ async function executeGoogle(request, opts) {
3439
3732
  });
3440
3733
  json = await res.json().catch(() => ({}));
3441
3734
  } catch (err) {
3442
- return retryableError(0, "network_error", String(err), null);
3735
+ return retryableError2(0, "network_error", String(err), null);
3443
3736
  }
3444
- if (!res.ok) return classifyHttpError(res.status, json);
3737
+ if (!res.ok) return classifyHttpError2(res.status, json);
3445
3738
  return { ok: true, status: res.status, response: normalizeGoogle(json) };
3446
3739
  }
3447
3740
  function normalizeGoogle(raw) {
@@ -3468,6 +3761,12 @@ async function executeOpenAI(request, opts) {
3468
3761
  if (!apiKey) {
3469
3762
  return terminalError(401, "auth", "OPENAI_API_KEY missing");
3470
3763
  }
3764
+ if (opts.onChunk) {
3765
+ return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
3766
+ onChunk: opts.onChunk,
3767
+ fetchImpl: opts.fetchImpl
3768
+ });
3769
+ }
3471
3770
  const { provider: _provider, ...body } = request;
3472
3771
  const fetchFn = opts.fetchImpl ?? fetch;
3473
3772
  let res;
@@ -3480,9 +3779,9 @@ async function executeOpenAI(request, opts) {
3480
3779
  });
3481
3780
  json = await res.json().catch(() => ({}));
3482
3781
  } catch (err) {
3483
- return retryableError(0, "network_error", String(err), null);
3782
+ return retryableError2(0, "network_error", String(err), null);
3484
3783
  }
3485
- if (!res.ok) return classifyHttpError(res.status, json);
3784
+ if (!res.ok) return classifyHttpError2(res.status, json);
3486
3785
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
3487
3786
  }
3488
3787
  async function executeDeepSeek(request, opts) {
@@ -3490,6 +3789,12 @@ async function executeDeepSeek(request, opts) {
3490
3789
  if (!apiKey) {
3491
3790
  return terminalError(401, "auth", "DEEPSEEK_API_KEY missing");
3492
3791
  }
3792
+ if (opts.onChunk) {
3793
+ return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
3794
+ onChunk: opts.onChunk,
3795
+ fetchImpl: opts.fetchImpl
3796
+ });
3797
+ }
3493
3798
  const { provider: _provider, ...body } = request;
3494
3799
  const fetchFn = opts.fetchImpl ?? fetch;
3495
3800
  let res;
@@ -3502,9 +3807,9 @@ async function executeDeepSeek(request, opts) {
3502
3807
  });
3503
3808
  json = await res.json().catch(() => ({}));
3504
3809
  } catch (err) {
3505
- return retryableError(0, "network_error", String(err), null);
3810
+ return retryableError2(0, "network_error", String(err), null);
3506
3811
  }
3507
- if (!res.ok) return classifyHttpError(res.status, json);
3812
+ if (!res.ok) return classifyHttpError2(res.status, json);
3508
3813
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
3509
3814
  }
3510
3815
  function normalizeOpenAILike(raw) {
@@ -3514,7 +3819,7 @@ function normalizeOpenAILike(raw) {
3514
3819
  const toolCalls = (choice?.message?.tool_calls ?? []).filter((tc) => tc.function?.name).map((tc, i) => ({
3515
3820
  id: tc.id ?? `tc-${i}`,
3516
3821
  name: tc.function.name,
3517
- args: tryParseJson(tc.function?.arguments) ?? {}
3822
+ args: tryParseJson2(tc.function?.arguments) ?? {}
3518
3823
  }));
3519
3824
  const u = r.usage ?? {};
3520
3825
  const tokens = {
@@ -3531,8 +3836,8 @@ function applyOverrides(request, overrides) {
3531
3836
  if (!layer) return request;
3532
3837
  return { ...request, ...layer };
3533
3838
  }
3534
- function classifyHttpError(status, body) {
3535
- const message = extractErrorMessage(body) ?? `HTTP ${status}`;
3839
+ function classifyHttpError2(status, body) {
3840
+ const message = extractErrorMessage2(body) ?? `HTTP ${status}`;
3536
3841
  if (status === 429) {
3537
3842
  return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
3538
3843
  }
@@ -3553,7 +3858,7 @@ function classifyHttpError(status, body) {
3553
3858
  }
3554
3859
  return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
3555
3860
  }
3556
- function extractErrorMessage(body) {
3861
+ function extractErrorMessage2(body) {
3557
3862
  if (!body || typeof body !== "object") return void 0;
3558
3863
  const b = body;
3559
3864
  if (b.error && typeof b.error === "object") {
@@ -3566,10 +3871,10 @@ function extractErrorMessage(body) {
3566
3871
  function terminalError(status, code, message) {
3567
3872
  return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
3568
3873
  }
3569
- function retryableError(status, code, message, raw) {
3874
+ function retryableError2(status, code, message, raw) {
3570
3875
  return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
3571
3876
  }
3572
- function tryParseJson(s) {
3877
+ function tryParseJson2(s) {
3573
3878
  if (typeof s !== "string" || s.length === 0) return void 0;
3574
3879
  try {
3575
3880
  const parsed = JSON.parse(s);
@@ -4383,10 +4688,13 @@ async function call(ir, opts = {}) {
4383
4688
  safeEmit(
4384
4689
  () => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
4385
4690
  );
4691
+ const targetSupportsStreaming = targetProfile?.streaming === true;
4692
+ const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
4386
4693
  const exec = await execute(activeCompile.request, {
4387
4694
  apiKeys: opts.apiKeys,
4388
4695
  fetchImpl: opts.fetchImpl,
4389
- providerOverrides: opts.providerOverrides
4696
+ providerOverrides: opts.providerOverrides,
4697
+ onChunk: streamingOnChunk
4390
4698
  });
4391
4699
  const validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
4392
4700
  if (validated.ok) {
package/dist/index.mjs CHANGED
@@ -1725,8 +1725,295 @@ var CallError = class extends Error {
1725
1725
  }
1726
1726
  };
1727
1727
 
1728
- // src/execute.ts
1728
+ // src/streaming.ts
1729
1729
  var ANTHROPIC_URL = "https://api.anthropic.com/v1/messages";
1730
+ async function streamAnthropic(request, apiKey, opts) {
1731
+ const { provider: _provider, ...body } = request;
1732
+ const fetchFn = opts.fetchImpl ?? fetch;
1733
+ let res;
1734
+ try {
1735
+ res = await fetchFn(ANTHROPIC_URL, {
1736
+ method: "POST",
1737
+ headers: {
1738
+ "x-api-key": apiKey,
1739
+ "anthropic-version": "2023-06-01",
1740
+ "content-type": "application/json"
1741
+ },
1742
+ body: JSON.stringify({ ...body, stream: true })
1743
+ });
1744
+ } catch (err) {
1745
+ return retryableError(0, "network_error", String(err), null);
1746
+ }
1747
+ if (!res.ok) {
1748
+ const errBody = await res.json().catch(() => ({}));
1749
+ return classifyHttpError(res.status, errBody);
1750
+ }
1751
+ let text = "";
1752
+ let inputTokens = 0;
1753
+ let outputTokens = 0;
1754
+ let cacheReadTokens;
1755
+ let cacheCreatedTokens;
1756
+ let stopReason;
1757
+ const toolBlocks = /* @__PURE__ */ new Map();
1758
+ try {
1759
+ await parseSSEStream(res, (event) => {
1760
+ if (!event.data) return;
1761
+ let payload;
1762
+ try {
1763
+ payload = JSON.parse(event.data);
1764
+ } catch {
1765
+ return;
1766
+ }
1767
+ const type = payload.type;
1768
+ if (type === "message_start") {
1769
+ const msg = payload.message;
1770
+ const usage = msg?.usage;
1771
+ if (usage) {
1772
+ inputTokens = usage.input_tokens ?? 0;
1773
+ outputTokens = usage.output_tokens ?? 0;
1774
+ if (typeof usage.cache_read_input_tokens === "number")
1775
+ cacheReadTokens = usage.cache_read_input_tokens;
1776
+ if (typeof usage.cache_creation_input_tokens === "number")
1777
+ cacheCreatedTokens = usage.cache_creation_input_tokens;
1778
+ }
1779
+ return;
1780
+ }
1781
+ if (type === "content_block_start") {
1782
+ const p = payload;
1783
+ const idx = p.index ?? 0;
1784
+ const block = p.content_block;
1785
+ if (block?.type === "tool_use" && block.id && block.name) {
1786
+ toolBlocks.set(idx, { id: block.id, name: block.name, argsJson: "" });
1787
+ }
1788
+ return;
1789
+ }
1790
+ if (type === "content_block_delta") {
1791
+ const p = payload;
1792
+ const delta = p.delta;
1793
+ if (!delta) return;
1794
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
1795
+ text += delta.text;
1796
+ opts.onChunk(delta.text);
1797
+ } else if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
1798
+ const idx = p.index ?? 0;
1799
+ const tool = toolBlocks.get(idx);
1800
+ if (tool) tool.argsJson += delta.partial_json;
1801
+ }
1802
+ return;
1803
+ }
1804
+ if (type === "message_delta") {
1805
+ const p = payload;
1806
+ if (p.delta?.stop_reason) stopReason = p.delta.stop_reason;
1807
+ if (typeof p.usage?.output_tokens === "number") outputTokens = p.usage.output_tokens;
1808
+ return;
1809
+ }
1810
+ });
1811
+ } catch (err) {
1812
+ return retryableError(0, "stream_interrupted", String(err), null);
1813
+ }
1814
+ const toolCalls = Array.from(toolBlocks.values()).map((b) => ({
1815
+ id: b.id,
1816
+ name: b.name,
1817
+ args: tryParseJson(b.argsJson) ?? {}
1818
+ }));
1819
+ const tokens = {
1820
+ input: inputTokens,
1821
+ output: outputTokens,
1822
+ total: inputTokens + outputTokens,
1823
+ cached: cacheReadTokens,
1824
+ cacheCreated: cacheCreatedTokens
1825
+ };
1826
+ const response = {
1827
+ text,
1828
+ structuredOutput: null,
1829
+ toolCalls,
1830
+ tokens,
1831
+ finishReason: stopReason,
1832
+ raw: { streamed: true, provider: "anthropic" }
1833
+ };
1834
+ return { ok: true, status: res.status, response };
1835
+ }
1836
+ async function streamOpenAILike(url, request, apiKey, providerLabel, opts) {
1837
+ const { provider: _provider, ...body } = request;
1838
+ const fetchFn = opts.fetchImpl ?? fetch;
1839
+ const reqBody = {
1840
+ ...body,
1841
+ stream: true,
1842
+ stream_options: {
1843
+ ...body.stream_options ?? {},
1844
+ include_usage: true
1845
+ }
1846
+ };
1847
+ let res;
1848
+ try {
1849
+ res = await fetchFn(url, {
1850
+ method: "POST",
1851
+ headers: {
1852
+ authorization: `Bearer ${apiKey}`,
1853
+ "content-type": "application/json"
1854
+ },
1855
+ body: JSON.stringify(reqBody)
1856
+ });
1857
+ } catch (err) {
1858
+ return retryableError(0, "network_error", String(err), null);
1859
+ }
1860
+ if (!res.ok) {
1861
+ const errBody = await res.json().catch(() => ({}));
1862
+ return classifyHttpError(res.status, errBody);
1863
+ }
1864
+ let text = "";
1865
+ let finishReason;
1866
+ let inputTokens = 0;
1867
+ let outputTokens = 0;
1868
+ let totalTokens;
1869
+ let cachedTokens;
1870
+ const toolBuffers = /* @__PURE__ */ new Map();
1871
+ try {
1872
+ await parseSSEStream(res, (event) => {
1873
+ if (!event.data) return;
1874
+ if (event.data === "[DONE]") return;
1875
+ let payload;
1876
+ try {
1877
+ payload = JSON.parse(event.data);
1878
+ } catch {
1879
+ return;
1880
+ }
1881
+ const choices = payload.choices;
1882
+ if (choices && choices.length > 0) {
1883
+ const choice = choices[0];
1884
+ const deltaContent = choice.delta?.content;
1885
+ if (typeof deltaContent === "string" && deltaContent.length > 0) {
1886
+ text += deltaContent;
1887
+ opts.onChunk(deltaContent);
1888
+ }
1889
+ const toolDeltas = choice.delta?.tool_calls;
1890
+ if (toolDeltas) {
1891
+ for (const tcDelta of toolDeltas) {
1892
+ const idx = tcDelta.index ?? 0;
1893
+ let buf = toolBuffers.get(idx);
1894
+ if (!buf) {
1895
+ buf = { id: tcDelta.id ?? `tc-${idx}`, name: "", argsJson: "" };
1896
+ toolBuffers.set(idx, buf);
1897
+ }
1898
+ if (tcDelta.id) buf.id = tcDelta.id;
1899
+ if (tcDelta.function?.name) buf.name += tcDelta.function.name;
1900
+ if (tcDelta.function?.arguments) buf.argsJson += tcDelta.function.arguments;
1901
+ }
1902
+ }
1903
+ if (choice.finish_reason) finishReason = choice.finish_reason;
1904
+ }
1905
+ const usage = payload.usage;
1906
+ if (usage) {
1907
+ if (typeof usage.prompt_tokens === "number") inputTokens = usage.prompt_tokens;
1908
+ if (typeof usage.completion_tokens === "number") outputTokens = usage.completion_tokens;
1909
+ if (typeof usage.total_tokens === "number") totalTokens = usage.total_tokens;
1910
+ const details = usage.prompt_tokens_details;
1911
+ if (typeof details?.cached_tokens === "number") cachedTokens = details.cached_tokens;
1912
+ }
1913
+ });
1914
+ } catch (err) {
1915
+ return retryableError(0, "stream_interrupted", String(err), null);
1916
+ }
1917
+ const toolCalls = Array.from(toolBuffers.values()).filter((b) => b.name.length > 0).map((b) => ({
1918
+ id: b.id,
1919
+ name: b.name,
1920
+ args: tryParseJson(b.argsJson) ?? {}
1921
+ }));
1922
+ const tokens = {
1923
+ input: inputTokens,
1924
+ output: outputTokens,
1925
+ total: totalTokens ?? inputTokens + outputTokens,
1926
+ cached: cachedTokens
1927
+ };
1928
+ const response = {
1929
+ text,
1930
+ structuredOutput: null,
1931
+ toolCalls,
1932
+ tokens,
1933
+ finishReason,
1934
+ raw: { streamed: true, provider: providerLabel }
1935
+ };
1936
+ return { ok: true, status: res.status, response };
1937
+ }
1938
+ async function parseSSEStream(response, handler) {
1939
+ const body = response.body;
1940
+ if (!body) throw new Error("Response has no body for SSE parse");
1941
+ const reader = body.getReader();
1942
+ const decoder = new TextDecoder("utf-8");
1943
+ let buffer = "";
1944
+ for (; ; ) {
1945
+ const { value, done } = await reader.read();
1946
+ if (done) break;
1947
+ buffer += decoder.decode(value, { stream: true });
1948
+ let sep;
1949
+ while (sep = buffer.indexOf("\n\n"), sep !== -1) {
1950
+ const block = buffer.slice(0, sep);
1951
+ buffer = buffer.slice(sep + 2);
1952
+ const event = parseSSEBlock(block);
1953
+ if (event) handler(event);
1954
+ }
1955
+ }
1956
+ if (buffer.length > 0) {
1957
+ const event = parseSSEBlock(buffer);
1958
+ if (event) handler(event);
1959
+ }
1960
+ }
1961
+ function parseSSEBlock(block) {
1962
+ const lines = block.split(/\r?\n/);
1963
+ let eventName;
1964
+ const dataLines = [];
1965
+ for (const line of lines) {
1966
+ if (line.startsWith(":")) continue;
1967
+ if (line.startsWith("event:")) {
1968
+ eventName = line.slice(6).trim();
1969
+ } else if (line.startsWith("data:")) {
1970
+ dataLines.push(line.slice(5).trim());
1971
+ }
1972
+ }
1973
+ if (dataLines.length === 0) return void 0;
1974
+ return { event: eventName, data: dataLines.join("\n") };
1975
+ }
1976
+ function tryParseJson(s) {
1977
+ if (typeof s !== "string" || s.length === 0) return void 0;
1978
+ try {
1979
+ const parsed = JSON.parse(s);
1980
+ return typeof parsed === "object" && parsed !== null ? parsed : void 0;
1981
+ } catch {
1982
+ return void 0;
1983
+ }
1984
+ }
1985
+ function classifyHttpError(status, body) {
1986
+ const message = extractErrorMessage(body) ?? `HTTP ${status}`;
1987
+ if (status === 429)
1988
+ return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
1989
+ if (status === 408)
1990
+ return { ok: false, status, errorType: "retryable", errorCode: "timeout", message, raw: body };
1991
+ if (status >= 500)
1992
+ return { ok: false, status, errorType: "retryable", errorCode: "server_error", message, raw: body };
1993
+ if (status === 404)
1994
+ return { ok: false, status, errorType: "retryable", errorCode: "model_not_found", message, raw: body };
1995
+ if (status === 401 || status === 403)
1996
+ return { ok: false, status, errorType: "terminal", errorCode: "auth", message, raw: body };
1997
+ if (status === 400)
1998
+ return { ok: false, status, errorType: "terminal", errorCode: "invalid_request", message, raw: body };
1999
+ return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
2000
+ }
2001
+ function extractErrorMessage(body) {
2002
+ if (!body || typeof body !== "object") return void 0;
2003
+ const b = body;
2004
+ if (b.error && typeof b.error === "object") {
2005
+ const e = b.error;
2006
+ if (typeof e.message === "string") return e.message;
2007
+ }
2008
+ if (typeof b.message === "string") return b.message;
2009
+ return void 0;
2010
+ }
2011
+ function retryableError(status, code, message, raw) {
2012
+ return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
2013
+ }
2014
+
2015
+ // src/execute.ts
2016
+ var ANTHROPIC_URL2 = "https://api.anthropic.com/v1/messages";
1730
2017
  var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
1731
2018
  var DEEPSEEK_URL = "https://api.deepseek.com/chat/completions";
1732
2019
  async function execute(request, opts = {}) {
@@ -1751,12 +2038,18 @@ async function executeAnthropic(request, opts) {
1751
2038
  if (!apiKey) {
1752
2039
  return terminalError(401, "auth", "ANTHROPIC_API_KEY missing");
1753
2040
  }
2041
+ if (opts.onChunk) {
2042
+ return streamAnthropic(request, apiKey, {
2043
+ onChunk: opts.onChunk,
2044
+ fetchImpl: opts.fetchImpl
2045
+ });
2046
+ }
1754
2047
  const { provider: _provider, ...body } = request;
1755
2048
  const fetchFn = opts.fetchImpl ?? fetch;
1756
2049
  let res;
1757
2050
  let json;
1758
2051
  try {
1759
- res = await fetchFn(ANTHROPIC_URL, {
2052
+ res = await fetchFn(ANTHROPIC_URL2, {
1760
2053
  method: "POST",
1761
2054
  headers: {
1762
2055
  "x-api-key": apiKey,
@@ -1767,9 +2060,9 @@ async function executeAnthropic(request, opts) {
1767
2060
  });
1768
2061
  json = await res.json().catch(() => ({}));
1769
2062
  } catch (err) {
1770
- return retryableError(0, "network_error", String(err), null);
2063
+ return retryableError2(0, "network_error", String(err), null);
1771
2064
  }
1772
- if (!res.ok) return classifyHttpError(res.status, json);
2065
+ if (!res.ok) return classifyHttpError2(res.status, json);
1773
2066
  return { ok: true, status: res.status, response: normalizeAnthropic(json) };
1774
2067
  }
1775
2068
  function normalizeAnthropic(raw) {
@@ -1803,9 +2096,9 @@ async function executeGoogle(request, opts) {
1803
2096
  });
1804
2097
  json = await res.json().catch(() => ({}));
1805
2098
  } catch (err) {
1806
- return retryableError(0, "network_error", String(err), null);
2099
+ return retryableError2(0, "network_error", String(err), null);
1807
2100
  }
1808
- if (!res.ok) return classifyHttpError(res.status, json);
2101
+ if (!res.ok) return classifyHttpError2(res.status, json);
1809
2102
  return { ok: true, status: res.status, response: normalizeGoogle(json) };
1810
2103
  }
1811
2104
  function normalizeGoogle(raw) {
@@ -1832,6 +2125,12 @@ async function executeOpenAI(request, opts) {
1832
2125
  if (!apiKey) {
1833
2126
  return terminalError(401, "auth", "OPENAI_API_KEY missing");
1834
2127
  }
2128
+ if (opts.onChunk) {
2129
+ return streamOpenAILike(OPENAI_URL, request, apiKey, "openai", {
2130
+ onChunk: opts.onChunk,
2131
+ fetchImpl: opts.fetchImpl
2132
+ });
2133
+ }
1835
2134
  const { provider: _provider, ...body } = request;
1836
2135
  const fetchFn = opts.fetchImpl ?? fetch;
1837
2136
  let res;
@@ -1844,9 +2143,9 @@ async function executeOpenAI(request, opts) {
1844
2143
  });
1845
2144
  json = await res.json().catch(() => ({}));
1846
2145
  } catch (err) {
1847
- return retryableError(0, "network_error", String(err), null);
2146
+ return retryableError2(0, "network_error", String(err), null);
1848
2147
  }
1849
- if (!res.ok) return classifyHttpError(res.status, json);
2148
+ if (!res.ok) return classifyHttpError2(res.status, json);
1850
2149
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
1851
2150
  }
1852
2151
  async function executeDeepSeek(request, opts) {
@@ -1854,6 +2153,12 @@ async function executeDeepSeek(request, opts) {
1854
2153
  if (!apiKey) {
1855
2154
  return terminalError(401, "auth", "DEEPSEEK_API_KEY missing");
1856
2155
  }
2156
+ if (opts.onChunk) {
2157
+ return streamOpenAILike(DEEPSEEK_URL, request, apiKey, "deepseek", {
2158
+ onChunk: opts.onChunk,
2159
+ fetchImpl: opts.fetchImpl
2160
+ });
2161
+ }
1857
2162
  const { provider: _provider, ...body } = request;
1858
2163
  const fetchFn = opts.fetchImpl ?? fetch;
1859
2164
  let res;
@@ -1866,9 +2171,9 @@ async function executeDeepSeek(request, opts) {
1866
2171
  });
1867
2172
  json = await res.json().catch(() => ({}));
1868
2173
  } catch (err) {
1869
- return retryableError(0, "network_error", String(err), null);
2174
+ return retryableError2(0, "network_error", String(err), null);
1870
2175
  }
1871
- if (!res.ok) return classifyHttpError(res.status, json);
2176
+ if (!res.ok) return classifyHttpError2(res.status, json);
1872
2177
  return { ok: true, status: res.status, response: normalizeOpenAILike(json) };
1873
2178
  }
1874
2179
  function normalizeOpenAILike(raw) {
@@ -1878,7 +2183,7 @@ function normalizeOpenAILike(raw) {
1878
2183
  const toolCalls = (choice?.message?.tool_calls ?? []).filter((tc) => tc.function?.name).map((tc, i) => ({
1879
2184
  id: tc.id ?? `tc-${i}`,
1880
2185
  name: tc.function.name,
1881
- args: tryParseJson(tc.function?.arguments) ?? {}
2186
+ args: tryParseJson2(tc.function?.arguments) ?? {}
1882
2187
  }));
1883
2188
  const u = r.usage ?? {};
1884
2189
  const tokens = {
@@ -1895,8 +2200,8 @@ function applyOverrides(request, overrides) {
1895
2200
  if (!layer) return request;
1896
2201
  return { ...request, ...layer };
1897
2202
  }
1898
- function classifyHttpError(status, body) {
1899
- const message = extractErrorMessage(body) ?? `HTTP ${status}`;
2203
+ function classifyHttpError2(status, body) {
2204
+ const message = extractErrorMessage2(body) ?? `HTTP ${status}`;
1900
2205
  if (status === 429) {
1901
2206
  return { ok: false, status, errorType: "retryable", errorCode: "rate_limit", message, raw: body };
1902
2207
  }
@@ -1917,7 +2222,7 @@ function classifyHttpError(status, body) {
1917
2222
  }
1918
2223
  return { ok: false, status, errorType: "terminal", errorCode: "unknown", message, raw: body };
1919
2224
  }
1920
- function extractErrorMessage(body) {
2225
+ function extractErrorMessage2(body) {
1921
2226
  if (!body || typeof body !== "object") return void 0;
1922
2227
  const b = body;
1923
2228
  if (b.error && typeof b.error === "object") {
@@ -1930,10 +2235,10 @@ function extractErrorMessage(body) {
1930
2235
  function terminalError(status, code, message) {
1931
2236
  return { ok: false, status, errorType: "terminal", errorCode: code, message, raw: null };
1932
2237
  }
1933
- function retryableError(status, code, message, raw) {
2238
+ function retryableError2(status, code, message, raw) {
1934
2239
  return { ok: false, status, errorType: "retryable", errorCode: code, message, raw };
1935
2240
  }
1936
- function tryParseJson(s) {
2241
+ function tryParseJson2(s) {
1937
2242
  if (typeof s !== "string" || s.length === 0) return void 0;
1938
2243
  try {
1939
2244
  const parsed = JSON.parse(s);
@@ -2103,10 +2408,13 @@ async function call(ir, opts = {}) {
2103
2408
  safeEmit(
2104
2409
  () => emitExecuteAttempt(traceId, ir.appId, { model: targetModel, attemptIndex: i })
2105
2410
  );
2411
+ const targetSupportsStreaming = targetProfile?.streaming === true;
2412
+ const streamingOnChunk = opts.onChunk && !opts.noStream && targetSupportsStreaming ? opts.onChunk : void 0;
2106
2413
  const exec = await execute(activeCompile.request, {
2107
2414
  apiKeys: opts.apiKeys,
2108
2415
  fetchImpl: opts.fetchImpl,
2109
- providerOverrides: opts.providerOverrides
2416
+ providerOverrides: opts.providerOverrides,
2417
+ onChunk: streamingOnChunk
2110
2418
  });
2111
2419
  const validated = exec.ok ? validateStructuredContract(exec, ir) : exec;
2112
2420
  if (validated.ok) {
@@ -733,6 +733,43 @@ interface CallOptions {
733
733
  * for hermetic test runs.
734
734
  */
735
735
  noAutoFilter?: boolean;
736
+ /**
737
+ * alpha.34. When provided AND the chosen provider supports streaming
738
+ * (`profile.streaming === true`) AND `noStream` is not set, kgauto
739
+ * enables provider-native SSE streaming at the wire layer and invokes
740
+ * `onChunk(delta)` once per provider stream event. `delta` is the text
741
+ * since the previous `onChunk` call (NOT cumulative) — provider
742
+ * stream-shape headache normalized in kgauto's lowering layer.
743
+ *
744
+ * `CallResult.response.text` is still populated with the full assembled
745
+ * response — kgauto buffers internally regardless of the callback.
746
+ * Consumers can use either the streaming side-effect OR the final
747
+ * `response.text`, doesn't matter.
748
+ *
749
+ * Tool calls + finish reason + usage are collected from stream events
750
+ * and returned in the final `CallResult` exactly as the non-streaming
751
+ * path would shape them. Brain telemetry latency = time-to-stream-end.
752
+ *
753
+ * Chain-walk semantics: if the streaming target fails mid-stream
754
+ * (network error, retryable provider error), kgauto walks to the next
755
+ * fallback target and restarts streaming from its first chunk —
756
+ * `onChunk` fires fresh from the new target. Consumer detects via the
757
+ * post-call `CallResult.fellOverFrom`. To opt out of fallback for
758
+ * streaming, set `noFallback: true` alongside `onChunk`.
759
+ *
760
+ * Filed by playbacksam s42 (2026-05-22) as `streaming-output-callback-
761
+ * on-callresult` for ComposeDrawer SSE; perceived-latency UX win
762
+ * during 10-30s draft assembly.
763
+ */
764
+ onChunk?: (chunk: string) => void;
765
+ /**
766
+ * alpha.34. Explicit opt-out of streaming even when `onChunk` is
767
+ * provided. Default: false (streaming enabled when `onChunk` is set).
768
+ * Use when the consumer wants to pass `onChunk` conditionally without
769
+ * branching the call site (e.g., capture chunks for instrumentation
770
+ * without engaging streaming wire format).
771
+ */
772
+ noStream?: boolean;
736
773
  }
737
774
  interface CallAttempt {
738
775
  model: string;
@@ -733,6 +733,43 @@ interface CallOptions {
733
733
  * for hermetic test runs.
734
734
  */
735
735
  noAutoFilter?: boolean;
736
+ /**
737
+ * alpha.34. When provided AND the chosen provider supports streaming
738
+ * (`profile.streaming === true`) AND `noStream` is not set, kgauto
739
+ * enables provider-native SSE streaming at the wire layer and invokes
740
+ * `onChunk(delta)` once per provider stream event. `delta` is the text
741
+ * since the previous `onChunk` call (NOT cumulative) — provider
742
+ * stream-shape headache normalized in kgauto's lowering layer.
743
+ *
744
+ * `CallResult.response.text` is still populated with the full assembled
745
+ * response — kgauto buffers internally regardless of the callback.
746
+ * Consumers can use either the streaming side-effect OR the final
747
+ * `response.text`, doesn't matter.
748
+ *
749
+ * Tool calls + finish reason + usage are collected from stream events
750
+ * and returned in the final `CallResult` exactly as the non-streaming
751
+ * path would shape them. Brain telemetry latency = time-to-stream-end.
752
+ *
753
+ * Chain-walk semantics: if the streaming target fails mid-stream
754
+ * (network error, retryable provider error), kgauto walks to the next
755
+ * fallback target and restarts streaming from its first chunk —
756
+ * `onChunk` fires fresh from the new target. Consumer detects via the
757
+ * post-call `CallResult.fellOverFrom`. To opt out of fallback for
758
+ * streaming, set `noFallback: true` alongside `onChunk`.
759
+ *
760
+ * Filed by playbacksam s42 (2026-05-22) as `streaming-output-callback-
761
+ * on-callresult` for ComposeDrawer SSE; perceived-latency UX win
762
+ * during 10-30s draft assembly.
763
+ */
764
+ onChunk?: (chunk: string) => void;
765
+ /**
766
+ * alpha.34. Explicit opt-out of streaming even when `onChunk` is
767
+ * provided. Default: false (streaming enabled when `onChunk` is set).
768
+ * Use when the consumer wants to pass `onChunk` conditionally without
769
+ * branching the call site (e.g., capture chunks for instrumentation
770
+ * without engaging streaming wire format).
771
+ */
772
+ noStream?: boolean;
736
773
  }
737
774
  interface CallAttempt {
738
775
  model: string;
@@ -1,4 +1,4 @@
1
- import { k as Provider } from './ir-DubC20e3.mjs';
1
+ import { k as Provider } from './ir-BAAHLfFL.mjs';
2
2
  import { IntentArchetypeName } from './dialect.mjs';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { k as Provider } from './ir-GnYPkpGo.js';
1
+ import { k as Provider } from './ir-B_ygNURd.js';
2
2
  import { IntentArchetypeName } from './dialect.js';
3
3
 
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { q as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, m as CallAttempt } from './ir-DubC20e3.mjs';
1
+ import { q as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, m as CallAttempt } from './ir-BAAHLfFL.mjs';
2
2
 
3
3
  /**
4
4
  * Glass-Box observability types (alpha.17).
@@ -1,4 +1,4 @@
1
- import { q as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, m as CallAttempt } from './ir-GnYPkpGo.js';
1
+ import { q as MutationApplied, B as BestPracticeAdvisory, F as FallbackReason, m as CallAttempt } from './ir-B_ygNURd.js';
2
2
 
3
3
  /**
4
4
  * Glass-Box observability types (alpha.17).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@warmdrift/kgauto-compiler",
3
- "version": "2.0.0-alpha.33",
3
+ "version": "2.0.0-alpha.34",
4
4
  "description": "Prompt compiler + central learning brain for multi-model AI apps. Swap models without rewriting prompts.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",