@qvac/harness 0.8.5 → 0.8.7

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +1278 -472
  2. package/README.md +49 -0
  3. package/dist-lib/index.d.ts +42 -2
  4. package/dist-lib/index.js +447 -58
  5. package/dist-lib/lib/audio-transcode-bare.d.ts +1 -1
  6. package/dist-lib/lib/audio-transcode-bare.js +22 -25
  7. package/dist-lib/lib/audio-transcode-node.d.ts +1 -1
  8. package/dist-lib/lib/audio-transcode-node.js +1 -3
  9. package/dist-lib/lib/capabilities/audio-resample.d.ts +1 -1
  10. package/dist-lib/lib/capabilities/audio-resample.js +7 -8
  11. package/dist-lib/lib/capabilities/completion.d.ts +6 -3
  12. package/dist-lib/lib/capabilities/completion.js +96 -49
  13. package/dist-lib/lib/capabilities/download.d.ts +3 -1
  14. package/dist-lib/lib/capabilities/download.js +1 -1
  15. package/dist-lib/lib/capabilities/preview-model-source.d.ts +1 -0
  16. package/dist-lib/lib/capabilities/preview-model-source.js +1 -0
  17. package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
  18. package/dist-lib/lib/capabilities/tool-selection.js +2 -4
  19. package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
  20. package/dist-lib/lib/capabilities/tool-turns.js +0 -6
  21. package/dist-lib/lib/capabilities/usage-breakdown.js +4 -1
  22. package/dist-lib/lib/capabilities/voice.d.ts +5 -1
  23. package/dist-lib/lib/capabilities/voice.js +94 -26
  24. package/dist-lib/lib/connect.d.ts +4 -1
  25. package/dist-lib/lib/connect.js +16 -1
  26. package/dist-lib/lib/contract-types.d.ts +12 -0
  27. package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
  28. package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
  29. package/dist-lib/lib/models/context-choice.d.ts +15 -0
  30. package/dist-lib/lib/models/context-choice.js +55 -0
  31. package/dist-lib/lib/models/download-gate.d.ts +20 -0
  32. package/dist-lib/lib/models/download-gate.js +73 -0
  33. package/dist-lib/lib/models/memory-cap.d.ts +13 -0
  34. package/dist-lib/lib/models/memory-cap.js +21 -0
  35. package/dist-lib/lib/models/model-catalog-constants.d.ts +3 -1
  36. package/dist-lib/lib/models/model-catalog-constants.js +9 -1
  37. package/dist-lib/lib/models/model-catalog.d.ts +6 -2
  38. package/dist-lib/lib/models/model-catalog.js +35 -3
  39. package/dist-lib/lib/models/model-handle.d.ts +8 -1
  40. package/dist-lib/lib/models/model-handle.js +20 -2
  41. package/dist-lib/lib/models/model-list.d.ts +8 -1
  42. package/dist-lib/lib/models/model-list.js +23 -0
  43. package/dist-lib/lib/models/model-pool.d.ts +35 -5
  44. package/dist-lib/lib/models/model-pool.js +97 -32
  45. package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
  46. package/dist-lib/lib/models/sdk-faults.js +6 -0
  47. package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
  48. package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
  49. package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
  50. package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
  51. package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
  52. package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
  53. package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
  54. package/dist-lib/lib/serve.js +23 -3
  55. package/dist-lib/lib/spawn.js +4 -10
  56. package/dist-lib/lib/tools/exec.d.ts +4 -1
  57. package/dist-lib/lib/tools/exec.js +55 -11
  58. package/dist-lib/lib/tools/generate-image.d.ts +4 -2
  59. package/dist-lib/lib/tools/generate-image.js +5 -2
  60. package/dist-lib/lib/tools/generate-music.d.ts +4 -2
  61. package/dist-lib/lib/tools/generate-music.js +5 -2
  62. package/dist-lib/lib/tools/office/ops.js +1 -1
  63. package/dist-lib/lib/tools/office.js +29 -6
  64. package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
  65. package/dist-lib/lib/tools/skills/catalog.js +2 -0
  66. package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
  67. package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
  68. package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
  69. package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
  70. package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
  71. package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
  72. package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
  73. package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
  74. package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
  75. package/dist-lib/lib/tools/skills/mention.js +0 -3
  76. package/dist-lib/lib/tools/translate.d.ts +3 -1
  77. package/dist-lib/lib/tools/translate.js +6 -1
  78. package/dist-lib/schema/harness.js +43 -4
  79. package/dist-lib/spec/bind.js +9 -0
  80. package/dist-lib/spec/calls.js +3 -0
  81. package/dist-lib/spec/hrpc/hrpc.json +39 -0
  82. package/dist-lib/spec/hrpc/index.js +39 -3
  83. package/dist-lib/spec/hrpc/messages.js +101 -13
  84. package/dist-lib/spec/hyperschema/index.js +101 -13
  85. package/dist-lib/spec/hyperschema/schema.json +62 -1
  86. package/dist-lib/spec/hyperschema/types.d.ts +20 -2
  87. package/package.json +8 -10
  88. package/worker/harness-sidecar.mjs +2 -1
@@ -1,2 +1,2 @@
1
+ export declare function decodeToPcm16kMono(input: Uint8Array): AsyncGenerator<Uint8Array>;
1
2
  export declare function transcodeToWav16kMono(input: Uint8Array): Promise<Uint8Array | null>;
2
- export declare function transcodeToPcm16kMono(input: Uint8Array): Promise<Uint8Array | null>;
@@ -92,13 +92,12 @@ const YIELD_EVERY_PACKETS = 32;
92
92
  function yieldToLoop() {
93
93
  return new Promise((resolve) => setTimeout(resolve, 0));
94
94
  }
95
- async function decodeToPcmChunks(inputFormat, { stream, decoder, rate }) {
96
- const chunks = [];
95
+ async function* decodeToPcmChunks(inputFormat, { stream, decoder, rate }) {
97
96
  const packet = new ffmpeg.Packet();
98
97
  const flush = new ffmpeg.Packet();
99
98
  const frame = new ffmpeg.Frame();
100
99
  const samples = new ffmpeg.Samples();
101
- const target = { frame, samples, chunks, rate };
100
+ const target = { frame, samples, chunks: [], rate };
102
101
  let resampler = null;
103
102
  let packets = 0;
104
103
  try {
@@ -107,13 +106,15 @@ async function decodeToPcmChunks(inputFormat, { stream, decoder, rate }) {
107
106
  resampler = drainDecodedFrames(decoder, resampler, target);
108
107
  }
109
108
  packet.unref();
109
+ yield* target.chunks.splice(0);
110
110
  if (++packets % YIELD_EVERY_PACKETS === 0) {
111
111
  await yieldToLoop();
112
112
  }
113
113
  }
114
114
  decoder.sendPacket(flush);
115
115
  resampler = drainDecodedFrames(decoder, resampler, target);
116
- chunks.push(...flushResampler(resampler, samples, rate));
116
+ yield* target.chunks.splice(0);
117
+ yield* flushResampler(resampler, samples, rate);
117
118
  }
118
119
  finally {
119
120
  resampler?.destroy();
@@ -121,35 +122,31 @@ async function decodeToPcmChunks(inputFormat, { stream, decoder, rate }) {
121
122
  flush.destroy();
122
123
  packet.destroy();
123
124
  }
124
- return chunks;
125
125
  }
126
- async function decodeInputToPcm(input, rate) {
127
- let inputFormat = null;
126
+ export async function* decodeToPcm16kMono(input) {
127
+ const inputFormat = new ffmpeg.InputFormatContext(bufferReadIO(input));
128
128
  let decoder = null;
129
129
  try {
130
- const format = new ffmpeg.InputFormatContext(bufferReadIO(input));
131
- inputFormat = format;
132
- const stream = format.getBestStream(ffmpeg.constants.mediaTypes.AUDIO);
130
+ const stream = inputFormat.getBestStream(ffmpeg.constants.mediaTypes.AUDIO);
133
131
  decoder = stream.decoder();
134
132
  decoder.open();
135
- const chunks = await decodeToPcmChunks(format, { stream, decoder, rate });
136
- return chunks.length === 0 ? null : b4a.concat(chunks);
137
- }
138
- catch (e) {
139
- hlog('audio decode/resample failed', { error: errorMessage(e) });
140
- return null;
133
+ yield* decodeToPcmChunks(inputFormat, { stream, decoder, rate: TARGET_SAMPLE_RATE });
141
134
  }
142
135
  finally {
143
- if (decoder) {
144
- decoder.destroy();
145
- }
146
- inputFormat?.destroy();
136
+ decoder?.destroy();
137
+ inputFormat.destroy();
147
138
  }
148
139
  }
149
140
  export async function transcodeToWav16kMono(input) {
150
- const pcm = await decodeInputToPcm(input, TARGET_SAMPLE_RATE);
151
- return pcm ? buildWavBuffer(pcm) : null;
152
- }
153
- export async function transcodeToPcm16kMono(input) {
154
- return decodeInputToPcm(input, TARGET_SAMPLE_RATE);
141
+ const chunks = [];
142
+ try {
143
+ for await (const chunk of decodeToPcm16kMono(input)) {
144
+ chunks.push(chunk);
145
+ }
146
+ }
147
+ catch (e) {
148
+ hlog('audio decode/resample failed', { error: errorMessage(e) });
149
+ return null;
150
+ }
151
+ return chunks.length === 0 ? null : buildWavBuffer(b4a.concat(chunks));
155
152
  }
@@ -1,2 +1,2 @@
1
1
  export declare function transcodeToWav16kMono(_input: Uint8Array): Promise<Uint8Array | null>;
2
- export declare function transcodeToPcm16kMono(_input: Uint8Array): Promise<Uint8Array | null>;
2
+ export declare function decodeToPcm16kMono(_input: Uint8Array): AsyncGenerator<Uint8Array>;
@@ -1,6 +1,4 @@
1
1
  export async function transcodeToWav16kMono(_input) {
2
2
  return null;
3
3
  }
4
- export async function transcodeToPcm16kMono(_input) {
5
- return null;
6
- }
4
+ export async function* decodeToPcm16kMono(_input) { }
@@ -11,5 +11,5 @@ export interface ResampledAudio {
11
11
  readonly transcoded: boolean;
12
12
  }
13
13
  export declare function resampleForTranscription(bytes: Uint8Array): Promise<ResampledAudio>;
14
- export declare function pcmForTranscription(bytes: Uint8Array): Promise<Uint8Array | null>;
14
+ export declare function pcmForTranscription(bytes: Uint8Array): AsyncGenerator<Uint8Array>;
15
15
  export {};
@@ -1,5 +1,5 @@
1
1
  import b4a from 'b4a';
2
- import { transcodeToPcm16kMono, transcodeToWav16kMono } from '#audio-transcode';
2
+ import { decodeToPcm16kMono, transcodeToWav16kMono } from '#audio-transcode';
3
3
  const TARGET_SAMPLE_RATE = 16000;
4
4
  const TARGET_CHANNELS = 1;
5
5
  const TARGET_BITS_PER_SAMPLE = 16;
@@ -73,12 +73,11 @@ function wavPcmData(bytes) {
73
73
  }
74
74
  return null;
75
75
  }
76
- export async function pcmForTranscription(bytes) {
77
- if (wavFormat(bytes) && !needsResample(bytes)) {
78
- const pcm = wavPcmData(bytes);
79
- if (pcm) {
80
- return pcm;
81
- }
76
+ export async function* pcmForTranscription(bytes) {
77
+ const pcm = wavFormat(bytes) && !needsResample(bytes) ? wavPcmData(bytes) : null;
78
+ if (pcm) {
79
+ yield pcm;
80
+ return;
82
81
  }
83
- return transcodeToPcm16kMono(bytes);
82
+ yield* decodeToPcm16kMono(bytes);
84
83
  }
@@ -1,4 +1,4 @@
1
- import type { CompletionFinal, Tool as SdkTool, ToolDialect } from '@qvac/sdk';
1
+ import type { CompletionEvent as SdkCompletionEvent, CompletionFinal, Tool as SdkTool, ToolDialect } from '@qvac/sdk';
2
2
  import type { AbortSignal as BareAbortSignal } from 'bare-abort-controller';
3
3
  import { type TextEvent } from '../helpers/thinking.ts';
4
4
  import type { ToolCallDisplay } from '../tool-call-display.ts';
@@ -125,6 +125,7 @@ export interface ToolExecuteContext {
125
125
  skills?: readonly string[] | undefined;
126
126
  skillsLoadedThisTurn?: readonly string[] | undefined;
127
127
  ctxSize?: number | undefined;
128
+ session?: string | undefined;
128
129
  image?: ImageSettings | undefined;
129
130
  settings?: Record<string, unknown> | undefined;
130
131
  turnImages?: {
@@ -266,7 +267,7 @@ export interface InferenceTokens {
266
267
  export type CompletionEvent = TextEvent | ToolCallEvent | ToolResultEvent | ToolActivityEvent | ErrorEvent | AbortedEvent | MetricsEvent | InferenceCallEvent | InferenceSentEvent;
267
268
  export interface CompletionSdkResult {
268
269
  requestId?: string;
269
- tokenStream: AsyncIterable<string>;
270
+ events: AsyncIterable<SdkCompletionEvent>;
270
271
  final: Promise<CompletionFinal>;
271
272
  }
272
273
  export type ResponseFormat = {
@@ -308,10 +309,12 @@ interface CompletionArgs {
308
309
  agent?: Agent;
309
310
  ctxSize?: number;
310
311
  cacheKey?: string;
312
+ session?: string;
311
313
  thinkingDefault?: string;
312
314
  generationParams?: LlmGenerationParams;
313
315
  responseFormat?: ResponseFormat;
314
316
  messages: Message[];
317
+ typedText?: string;
315
318
  tools?: ToolDefinition[];
316
319
  skillCatalog?: readonly CompletionSkillEntry[];
317
320
  approve?: ApproveFn;
@@ -332,5 +335,5 @@ export declare function toSdkTool(tool: ToolDefinition): SdkTool;
332
335
  export type CompletionSkillEntry = SkillCatalogEntry & {
333
336
  readonly status?: SkillState;
334
337
  };
335
- export declare function completion(sdk: CompletionSdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, tools: allTools, skillCatalog, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots, fsWriteRoots, captureInferenceCalls }: CompletionArgs): AsyncGenerator<CompletionEvent>;
338
+ export declare function completion(sdk: CompletionSdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, session, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, typedText, tools: allTools, skillCatalog, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots, fsWriteRoots, captureInferenceCalls }: CompletionArgs): AsyncGenerator<CompletionEvent>;
336
339
  export {};
@@ -8,14 +8,14 @@ import { AbortedError, raceAbort } from '../helpers/race-abort.js';
8
8
  import { raceTimeout } from '../helpers/race-timeout.js';
9
9
  import { createRunOutcomeTracker, degenerateAnswer, unnamedWrites, writeReport } from '../helpers/run-outcome.js';
10
10
  import { splitThinking } from '../helpers/thinking.js';
11
- import { clipRawCall, normalizeToolArgs, recoverMarkupToolCalls, recoverPrintedSkillLoad, recoverSkillNamedCall, stripToolCallMarkup, stripToolResponseBlocks, toolCallMarkup } from '../helpers/tool-call-markup.js';
11
+ import { clipRawCall, hasMarkupCall, normalizeToolArgs, recoverMarkupToolCalls, recoverPrintedSkillLoad, recoverSkillNamedCall, stripToolCallMarkup, stripToolResponseBlocks, toolCallMarkup } from '../helpers/tool-call-markup.js';
12
12
  import { INTENT_PARAM, INTENT_PARAM_DESCRIPTION, takeToolIntent } from '../helpers/tool-intent.js';
13
13
  import { HookPipeline } from '../hooks/pipeline.js';
14
14
  import { fallbackToolLabel } from '../helpers/tool-label.js';
15
15
  import { hlog } from '../log.js';
16
16
  import { resolveLlmGenerationParams } from '../models/llm-presets.js';
17
- import { customToolDialect } from '../models/model-catalog.js';
18
- import { isContextOverflowError, isSdkModelWedgedError } from '../models/sdk-faults.js';
17
+ import { toolDialectFor } from '../models/model-catalog.js';
18
+ import { isContextOverflowError, isGrammarOverrunError, isSdkModelWedgedError } from '../models/sdk-faults.js';
19
19
  import { annotateLastUserTime, skillsPromptSection, systemPromptParts } from '../prompt.js';
20
20
  import { isMetadataMethod } from '../tools/mcp/protocol.js';
21
21
  import { unquoteArg } from '../tools/model-args.js';
@@ -25,11 +25,11 @@ import { promptUsageParts, usageBreakdown } from './usage-breakdown.js';
25
25
  import { collectToolGrants, enabledTools, isSelected, skillIsActive, skillsAreSelected } from './tool-selection.js';
26
26
  import { guardToolCall } from '../tools/outbound-guard.js';
27
27
  import { isAvailableState } from '../tools/skills/skill-state-constants.js';
28
- import { resolveImplicitSkill, resolveSkillMention, skillPreloadMessage } from '../tools/skills/mention.js';
28
+ import { resolveImplicitSkill, resolveSkillMention } from '../tools/skills/mention.js';
29
29
  import { NETWORK_TOOLSETS, permittedTools } from '../tools/toolsets.js';
30
30
  import { promptPrefixHash } from './prompt-prefix.js';
31
31
  import { logToolPrompt } from './tool-prompt-log.js';
32
- import { DIRECT_CALL_ID, deliversFile, FILE_DELIVERED_NUDGE, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn, withoutSkillLoadTurns } from './tool-turns.js';
32
+ import { DIRECT_CALL_ID, deliversFile, FILE_DELIVERED_NUDGE, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn } from './tool-turns.js';
33
33
  export function imageSettingsFrom(agent) {
34
34
  if (!agent)
35
35
  return undefined;
@@ -139,14 +139,11 @@ function explicitlyDisablesWebSearch(text) {
139
139
  return (/\b(?:do\s+not|don't|dont|never)\s+(?:search|browse|look\s+up|find|use)\s+(?:the\s+)?(?:web|internet|online)\b/i.test(text) ||
140
140
  /\b(?:avoid|without)\s+(?:searching|browsing|using)\s+(?:the\s+)?(?:web|internet|online)\b/i.test(text));
141
141
  }
142
- function directToolHistory(history, args, output) {
142
+ function directToolHistory(history, call, output) {
143
143
  return [
144
144
  ...history,
145
- {
146
- role: 'assistant',
147
- content: toolCallMarkup([{ name: WEB_SEARCH_TOOL_NAME, arguments: args }])
148
- },
149
- { role: 'tool', content: toolResultTurn(WEB_SEARCH_TOOL_NAME, DIRECT_CALL_ID, output) }
145
+ { role: 'assistant', content: toolCallMarkup([call]) },
146
+ { role: 'tool', content: toolResultTurn(call.name, DIRECT_CALL_ID, output) }
150
147
  ];
151
148
  }
152
149
  function assistantToolTurn(contentText, calls) {
@@ -155,18 +152,6 @@ function assistantToolTurn(contentText, calls) {
155
152
  const markup = toolCallMarkup(calls);
156
153
  return contentText ? `${contentText}\n\n${markup}` : markup;
157
154
  }
158
- function preloadLastUser(messages, name, body) {
159
- for (let i = messages.length - 1; i >= 0; i--) {
160
- const turn = messages[i];
161
- if (turn?.role !== 'user') {
162
- continue;
163
- }
164
- const next = [...messages];
165
- next[i] = { ...turn, content: skillPreloadMessage(turn.content, name, body) };
166
- return next;
167
- }
168
- return messages;
169
- }
170
155
  function collectStats(state, stats, stopReason) {
171
156
  state.stopReason = stopReason;
172
157
  if (!stats) {
@@ -362,10 +347,11 @@ async function executeCall(tool, args, { ctx, approve, signal, denied, sources =
362
347
  return executedCall(output, outcome);
363
348
  }
364
349
  function runToolContext(scope) {
365
- const { agent, attach, fetchAttachment, ragSearch, progress, askUser, signal, runBudget } = scope;
350
+ const { agent, session, attach, fetchAttachment, ragSearch, progress, askUser, signal, runBudget } = scope;
366
351
  const image = imageSettingsFrom(agent);
367
352
  return (turnImages = [], ctxSize) => ({
368
353
  agentId: agent?.id ?? null,
354
+ session,
369
355
  ctxSize,
370
356
  image,
371
357
  turnImages: turnImages.length ? turnImages : undefined,
@@ -459,6 +445,27 @@ function unprovenReplay(state, final) {
459
445
  }
460
446
  return state.unproven;
461
447
  }
448
+ async function* contentDeltas(events) {
449
+ for await (const event of events) {
450
+ if (event.type === 'contentDelta') {
451
+ yield event.text;
452
+ }
453
+ }
454
+ }
455
+ async function* endAtGrammarOverrun(tokens, state) {
456
+ try {
457
+ for await (const token of tokens) {
458
+ state.rawText += token;
459
+ yield token;
460
+ }
461
+ }
462
+ catch (err) {
463
+ if (!isGrammarOverrunError(err)) {
464
+ throw err;
465
+ }
466
+ state.overrun = err;
467
+ }
468
+ }
462
469
  async function* runRoundTokens(result, state, { sdk, signal, startInThink = false }) {
463
470
  const cancelThisRound = () => {
464
471
  if (result.requestId) {
@@ -466,7 +473,8 @@ async function* runRoundTokens(result, state, { sdk, signal, startInThink = fals
466
473
  }
467
474
  };
468
475
  signal?.addEventListener('abort', cancelThisRound, { once: true });
469
- const tokens = stopAtControlToken(result.tokenStream, (token) => {
476
+ const deltas = endAtGrammarOverrun(contentDeltas(result.events), state);
477
+ const tokens = stopAtControlToken(deltas, (token) => {
470
478
  state.stopToken = token;
471
479
  });
472
480
  let tokenEvents = 0;
@@ -501,6 +509,9 @@ async function* runRoundTokens(result, state, { sdk, signal, startInThink = fals
501
509
  if (contentTail) {
502
510
  yield { type: 'content', text: contentTail };
503
511
  }
512
+ if (state.overrun) {
513
+ throw state.overrun;
514
+ }
504
515
  hlog('stream ended', { events: tokenEvents, stopToken: state.stopToken });
505
516
  if (state.stopToken) {
506
517
  cancelThisRound();
@@ -526,9 +537,12 @@ function roundTokens(stats) {
526
537
  };
527
538
  return Object.values(tokens).some((value) => value !== undefined) ? tokens : undefined;
528
539
  }
540
+ function acceptedFinal(contentText, fullText = contentText) {
541
+ return { contentText, toolCalls: [], raw: { fullText } };
542
+ }
529
543
  const TRUNCATED_FINAL_MS = 2000;
530
544
  function truncatedFinal(final, contentText) {
531
- const accepted = { contentText, toolCalls: [], raw: { fullText: contentText } };
545
+ const accepted = acceptedFinal(contentText);
532
546
  const guarded = final.catch((err) => {
533
547
  if (isSdkModelWedgedError(err))
534
548
  throw err;
@@ -553,7 +567,13 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
553
567
  throw new AbortedError();
554
568
  let contentText = '';
555
569
  let emitted = false;
556
- const state = { stopToken: null, unproven: null, rawContentText: '' };
570
+ const state = {
571
+ stopToken: null,
572
+ unproven: null,
573
+ rawContentText: '',
574
+ rawText: '',
575
+ overrun: null
576
+ };
557
577
  const cacheable = candidate.history === args.history;
558
578
  const params = {
559
579
  modelId,
@@ -626,6 +646,14 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
626
646
  };
627
647
  }
628
648
  catch (err) {
649
+ const recovered = isGrammarOverrunError(err) && hasMarkupCall(state.rawText);
650
+ let outcome = 'failed';
651
+ if (recovered) {
652
+ outcome = 'completed';
653
+ }
654
+ else if (isContextOverflowError(err)) {
655
+ outcome = 'overflowed';
656
+ }
629
657
  if (args.capture) {
630
658
  yield {
631
659
  type: 'inference-sent',
@@ -633,10 +661,21 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
633
661
  droppedTurns: candidate.droppedTurns,
634
662
  messages: undefined,
635
663
  tokens: undefined,
636
- outcome: isContextOverflowError(err) ? 'overflowed' : 'failed',
664
+ outcome,
637
665
  reason: errorMessage(err)
638
666
  };
639
667
  }
668
+ if (recovered) {
669
+ hlog('grammar overrun recovered', { round: args.round });
670
+ return {
671
+ contentText,
672
+ final: acceptedFinal(state.rawContentText, state.rawText),
673
+ history: candidate.history,
674
+ cacheable,
675
+ unproven: state.unproven,
676
+ replay: null
677
+ };
678
+ }
640
679
  if (!isContextOverflowError(err)) {
641
680
  throw err;
642
681
  }
@@ -657,7 +696,7 @@ function* reportUnnamedWrites(said, landed) {
657
696
  yield { type: 'content', text: unreported };
658
697
  }
659
698
  }
660
- export async function* completion(sdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, tools: allTools = [], skillCatalog = [], approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots = [], fsWriteRoots = [], captureInferenceCalls = false }) {
699
+ export async function* completion(sdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, session, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, typedText, tools: allTools = [], skillCatalog = [], approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots = [], fsWriteRoots = [], captureInferenceCalls = false }) {
661
700
  const pipeline = new HookPipeline();
662
701
  const agentId = agent?.id ?? null;
663
702
  const disabledToolsets = agent?.sandboxMode
@@ -669,6 +708,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
669
708
  ...fsRoots.filter((root) => root.kind === 'hyper' && root.writable && !fsWriteRoots.includes(root))
670
709
  ];
671
710
  const messages = storedMessages.map(sanitizeTurn);
711
+ const userText = typedText === undefined ? lastUserContent(messages) : stripControlTokens(typedText);
672
712
  const stats = { tokens: 0, timedTokens: 0, durationMs: 0, ctxSize, backends: [] };
673
713
  let runOutcome = 'abandoned';
674
714
  let runError;
@@ -684,6 +724,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
684
724
  };
685
725
  const toolContext = runToolContext({
686
726
  agent,
727
+ session: session ?? sessionOf(cacheKey),
687
728
  attach,
688
729
  fetchAttachment,
689
730
  ragSearch,
@@ -696,16 +737,14 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
696
737
  const toolsSelected = isSelected(agent?.toolsScope, agent?.enabledTools);
697
738
  const skillsSelected = skillsAreSelected(agent?.skillsScope, Boolean(agent));
698
739
  const inContextSkills = skillCatalog.filter(skillInContext);
699
- const mention = resolveSkillMention(lastUserContent(messages), inContextSkills);
700
740
  const loadedSkills = loadedSkillsFrom(messages);
701
741
  const loadedSkillFiles = loadedSkillFilesFrom(messages);
702
742
  const activation = {
703
743
  skillsSelected,
704
- enabledSkills: agent?.enabledSkills ?? [],
705
- mention: mention?.name ?? null,
706
- loadedSkills
744
+ enabledSkills: agent?.enabledSkills ?? []
707
745
  };
708
746
  const activeSkills = inContextSkills.filter((skill) => skillIsActive(skill.name, activation));
747
+ const mention = resolveSkillMention(userText, activeSkills);
709
748
  const { grantedNames, grantedOwners, grantedOps } = collectToolGrants(activeSkills);
710
749
  function selectEnabled() {
711
750
  if (responseFormat)
@@ -753,17 +792,16 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
753
792
  }
754
793
  skillIcon = skillEmoji(skillName) ?? skillIcon;
755
794
  }
756
- const toolDialect = model ? customToolDialect(model) : undefined;
757
- let turns = messages;
795
+ const toolDialect = model ? toolDialectFor(model) : undefined;
758
796
  let preloadedSkill = null;
759
- const preload = mention ?? resolveImplicitSkill(lastUserContent(messages), activeSkills);
797
+ let preloadedResult = null;
798
+ const preload = mention ?? resolveImplicitSkill(userText, activeSkills);
760
799
  if (preload) {
761
800
  const loader = tools.find((tool) => tool.name === SKILL_TOOL_NAME);
762
801
  const loaded = loader ? await loader.execute({ name: preload.name }, toolContext()) : null;
763
802
  if (loaded && typeof loaded === 'object' && 'content' in loaded) {
764
803
  preloadedSkill = String(loaded.content);
765
- const inlined = preloadLastUser(messages, preload.name, preloadedSkill);
766
- turns = withoutSkillLoadTurns(inlined, preload.name);
804
+ preloadedResult = loaded;
767
805
  if (!loadedSkills.has(preload.name)) {
768
806
  const preloadArgs = { name: preload.name };
769
807
  const preloadEffect = 'read';
@@ -791,6 +829,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
791
829
  effect: preloadEffect,
792
830
  resource: preloadResource
793
831
  });
832
+ preloadedResult = settled.result;
794
833
  yield {
795
834
  type: 'tool-result',
796
835
  name: SKILL_TOOL_NAME,
@@ -833,18 +872,19 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
833
872
  });
834
873
  }
835
874
  logSelection();
836
- let history = annotateLastUserTime(systemContent ? [{ role: 'system', content: systemContent }, ...turns] : turns);
837
- let roundCacheKey = preloadedSkill === null ? cacheKey : undefined;
875
+ let history = annotateLastUserTime(systemContent ? [{ role: 'system', content: systemContent }, ...messages] : messages);
876
+ if (preload && preloadedSkill !== null) {
877
+ history = directToolHistory(history, { name: SKILL_TOOL_NAME, arguments: { name: preload.name } }, preloadedResult);
878
+ history.push({ role: 'user', content: TOOL_CONTINUATION_NUDGE });
879
+ }
880
+ let roundCacheKey = cacheKey;
838
881
  const runUsage = () => usageBreakdown(promptParts, stats.cacheTokens, model);
839
882
  const denied = new Set();
840
883
  const failed = new Map();
841
884
  let blockedRepeats = 0;
842
885
  let webSearches = 0;
843
- const sources = trustedSourcesFrom(messages);
844
- if (preloadedSkill) {
845
- sources.push({ origin: 'skill', text: preloadedSkill });
846
- }
847
- const query = explicitWebSearchQuery(lastUserContent(messages), enabled, {
886
+ const sources = trustedSourcesFrom(history);
887
+ const query = explicitWebSearchQuery(userText, enabled, {
848
888
  skillPreloaded: preloadedSkill !== null
849
889
  });
850
890
  const webSearch = enabled.find((tool) => tool.name === WEB_SEARCH_TOOL_NAME);
@@ -917,7 +957,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
917
957
  name: webSearch.name,
918
958
  result: output
919
959
  };
920
- history = directToolHistory(history, args, output);
960
+ history = directToolHistory(history, { name: webSearch.name, arguments: args }, output);
921
961
  }
922
962
  let outcome = 'exhausted';
923
963
  let said = '';
@@ -1021,6 +1061,11 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1021
1061
  break;
1022
1062
  }
1023
1063
  hlog('round produced no answer', { round, mute });
1064
+ history = [
1065
+ ...history,
1066
+ { role: 'assistant', content: spoken },
1067
+ { role: 'user', content: TOOL_CONTINUATION_NUDGE }
1068
+ ];
1024
1069
  if (mute++ < MAX_MUTE_ROUNDS) {
1025
1070
  continue;
1026
1071
  }
@@ -1058,9 +1103,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1058
1103
  const searchCapped = tool?.name === WEB_SEARCH_TOOL_NAME && webSearches >= MAX_WEB_SEARCHES_PER_TURN;
1059
1104
  const callCtx = {
1060
1105
  ...toolContext(turnImages, ctxSize),
1061
- skills: [...loadedSkills].filter((name) => !skillsSelected ||
1062
- (agent?.enabledSkills ?? []).includes(name) ||
1063
- name === mention?.name),
1106
+ skills: [...loadedSkills].filter((name) => skillIsActive(name, activation)),
1064
1107
  skillsLoadedThisTurn: [...loadedThisTurn],
1065
1108
  settings: toolConfigs?.[call.name],
1066
1109
  toolOps: grantedOps,
@@ -1325,6 +1368,10 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
1325
1368
  });
1326
1369
  }
1327
1370
  }
1371
+ function sessionOf(cacheKey) {
1372
+ const base = cacheKey?.split('/')[0];
1373
+ return base ? base : undefined;
1374
+ }
1328
1375
  function toolDecision({ auto, skipAll, approved }) {
1329
1376
  if (auto)
1330
1377
  return 'auto';
@@ -45,4 +45,6 @@ export type SdkOrLoader = DownloadSdk | (() => Promise<DownloadSdk>);
45
45
  export interface DownloadEventStream extends Readable {
46
46
  [Symbol.asyncIterator](): AsyncIterableIterator<DownloadEvent>;
47
47
  }
48
- export declare function download(sdkOrLoader: SdkOrLoader, args: DownloadArgs, onSettled?: () => void): DownloadEventStream;
48
+ type TerminalEvent = Exclude<DownloadEvent, DownloadProgressEvent>;
49
+ export declare function download(sdkOrLoader: SdkOrLoader, args: DownloadArgs, onSettled?: (event: TerminalEvent) => void): DownloadEventStream;
50
+ export {};
@@ -17,7 +17,7 @@ export function download(sdkOrLoader, args, onSettled) {
17
17
  settled = true;
18
18
  stream.push(event);
19
19
  stream.push(null);
20
- onSettled?.();
20
+ onSettled?.(event);
21
21
  };
22
22
  const cancelOp = () => {
23
23
  if (cancelSent || opDone || !requestId || !sdkRef?.cancel)
@@ -41,6 +41,7 @@ export type PreviewFetch = (url: string, init?: {
41
41
  redirect?: 'follow';
42
42
  }) => Promise<PreviewResponse>;
43
43
  export interface FitQuery {
44
+ src: string;
44
45
  sha256Checksum: string;
45
46
  name: string;
46
47
  contextTokens: number;
@@ -365,6 +365,7 @@ async function assess({ file, header }, taskType, deps) {
365
365
  }
366
366
  try {
367
367
  return await deps.assessFit({
368
+ src: file.src,
368
369
  sha256Checksum: file.sha256,
369
370
  name: header.name ?? file.label,
370
371
  contextTokens: defaultContextSize(file.src, header.contextLength)
@@ -18,11 +18,9 @@ export declare function collectToolGrants<T extends {
18
18
  export interface SkillActivation {
19
19
  skillsSelected: boolean;
20
20
  enabledSkills: ReadonlyArray<string>;
21
- mention: string | null;
22
- loadedSkills: ReadonlySet<string>;
23
21
  }
24
22
  export declare function skillsAreSelected(scope: 'all' | 'selected' | null | undefined, hasAgent: boolean): boolean;
25
- export declare function skillIsActive(name: string, { skillsSelected, enabledSkills, mention, loadedSkills }: SkillActivation): boolean;
23
+ export declare function skillIsActive(name: string, { skillsSelected, enabledSkills }: SkillActivation): boolean;
26
24
  export interface SelectableTool {
27
25
  name: string;
28
26
  skillGated?: boolean | undefined;
@@ -25,10 +25,8 @@ export function collectToolGrants(skills) {
25
25
  export function skillsAreSelected(scope, hasAgent) {
26
26
  return hasAgent && scope !== 'all';
27
27
  }
28
- export function skillIsActive(name, { skillsSelected, enabledSkills, mention, loadedSkills }) {
29
- if (!skillsSelected)
30
- return true;
31
- return enabledSkills.includes(name) || name === mention || loadedSkills.has(name);
28
+ export function skillIsActive(name, { skillsSelected, enabledSkills }) {
29
+ return !skillsSelected || enabledSkills.includes(name);
32
30
  }
33
31
  export function isSelected(scope, list) {
34
32
  return (scope ?? (list?.length ? 'selected' : 'all')) === 'selected';
@@ -9,4 +9,3 @@ export declare function skillLoadedTurn(name: string): string;
9
9
  export declare function attachmentTurn(name: string, result: unknown): string | null;
10
10
  export declare function loadedSkillsFrom(messages: readonly Message[]): Set<string>;
11
11
  export declare function loadedSkillFilesFrom(messages: readonly Message[]): Map<string, Set<string>>;
12
- export declare function withoutSkillLoadTurns(messages: readonly Message[], name: string): Message[];
@@ -71,12 +71,6 @@ export function loadedSkillFilesFrom(messages) {
71
71
  }
72
72
  return loaded;
73
73
  }
74
- export function withoutSkillLoadTurns(messages, name) {
75
- return messages.filter((turn) => {
76
- const payload = skillLoadPayload(turn);
77
- return !payload || payload.name !== name || payload.file !== undefined;
78
- });
79
- }
80
74
  function* skillLoadPayloads(messages) {
81
75
  for (const turn of messages) {
82
76
  const payload = skillLoadPayload(turn);
@@ -1,8 +1,11 @@
1
+ import { toolDialectFor } from '../models/model-catalog.js';
1
2
  import { detectToolDialect } from '../models/tool-dialect.js';
2
3
  const QWEN35 = { prose: 4.38, schema: 4.08, toolBlockTokens: 189 };
3
4
  const DENSITY_BY_DIALECT = new Map([['qwen35', QWEN35]]);
4
5
  export function densityFor(modelName) {
5
- const dialect = modelName ? detectToolDialect(modelName, modelName) : undefined;
6
+ const dialect = modelName
7
+ ? (toolDialectFor(modelName) ?? detectToolDialect(modelName, modelName))
8
+ : undefined;
6
9
  return (dialect && DENSITY_BY_DIALECT.get(dialect)) ?? QWEN35;
7
10
  }
8
11
  export function promptUsageParts({ system, toolSchemas }) {
@@ -75,7 +75,11 @@ export interface TranscribeStreamingOptions {
75
75
  modelId: string;
76
76
  signal?: BareAbortSignal | undefined;
77
77
  }
78
- export declare function transcribeStreaming(sdk: VoiceSdk, pcm: Uint8Array, { modelId, signal }: TranscribeStreamingOptions): Promise<string>;
78
+ export interface TranscribeAudioOptions extends TranscribeStreamingOptions {
79
+ audio: Buffer;
80
+ }
81
+ export declare function transcribeAudio(sdk: VoiceSdk, pcm: AsyncIterable<Uint8Array>, { modelId, signal, audio }: TranscribeAudioOptions): Promise<string>;
82
+ export declare function transcribeStreaming(sdk: VoiceSdk, pcm: AsyncIterable<Uint8Array>, opts: TranscribeStreamingOptions): Promise<string>;
79
83
  export declare function speakStreamOnce(sdk: VoiceSdk, modelId: string, text: AsyncIterable<string>): AsyncGenerator<SpeakResult>;
80
84
  export declare function speakable(text: string): string;
81
85
  export declare function speakOnce(sdk: VoiceSdk, modelId: string, text: string): Promise<number[]>;