@mrciphersmith/keryx 0.2.49 → 0.2.50

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/cli.js CHANGED
@@ -186,14 +186,14 @@ var init_config_dir = __esm(() => {
186
186
  // src/lib/fs.ts
187
187
  var exports_fs = {};
188
188
  __export(exports_fs, {
189
- writeFileAtomic: () => writeFileAtomic,
190
- withFileLock: () => withFileLock2,
191
- toPosix: () => toPosix,
192
- pathExists: () => pathExists,
193
- isPathInside: () => isPathInside,
194
- isNotFound: () => isNotFound,
189
+ DEFAULT_LOCK_STALE_MS: () => DEFAULT_LOCK_STALE_MS,
195
190
  isLockHeld: () => isLockHeld,
196
- DEFAULT_LOCK_STALE_MS: () => DEFAULT_LOCK_STALE_MS
191
+ isNotFound: () => isNotFound,
192
+ isPathInside: () => isPathInside,
193
+ pathExists: () => pathExists,
194
+ toPosix: () => toPosix,
195
+ withFileLock: () => withFileLock2,
196
+ writeFileAtomic: () => writeFileAtomic
197
197
  });
198
198
  import { randomUUID as randomUUID4 } from "crypto";
199
199
  import { access, mkdir, readFile, rename, rm, stat, utimes, writeFile } from "fs/promises";
@@ -5372,13 +5372,13 @@ var init_service2 = __esm(() => {
5372
5372
  // src/lib/shell-config.ts
5373
5373
  var exports_shell_config = {};
5374
5374
  __export(exports_shell_config, {
5375
- shellConfigPath: () => shellConfigPath,
5376
- saveShellConfig: () => saveShellConfig,
5377
- saveProviderBaseUrl: () => saveProviderBaseUrl,
5378
- saveApiKey: () => saveApiKey,
5379
- loadShellConfig: () => loadShellConfig,
5375
+ applySavedApiKeys: () => applySavedApiKeys,
5380
5376
  envWithSavedApiKeys: () => envWithSavedApiKeys,
5381
- applySavedApiKeys: () => applySavedApiKeys
5377
+ loadShellConfig: () => loadShellConfig,
5378
+ saveApiKey: () => saveApiKey,
5379
+ saveProviderBaseUrl: () => saveProviderBaseUrl,
5380
+ saveShellConfig: () => saveShellConfig,
5381
+ shellConfigPath: () => shellConfigPath
5382
5382
  });
5383
5383
  import { existsSync as existsSync7 } from "fs";
5384
5384
  import path35 from "path";
@@ -5987,6 +5987,40 @@ function createTreesitterSpec(cwd, config) {
5987
5987
  load: (ctx) => new TreesitterAdapter(cwd, config, ctx.dep)
5988
5988
  };
5989
5989
  }
5990
+ async function loadTreesitterDepLiteral() {
5991
+ try {
5992
+ return await import("web-tree-sitter");
5993
+ } catch {
5994
+ return;
5995
+ }
5996
+ }
5997
+ async function resolveTreesitterCapability(cwd, config, resolve = resolveCapability) {
5998
+ const spec = createTreesitterSpec(cwd, config);
5999
+ try {
6000
+ if (!await isCapabilityEnabled(cwd, spec.id)) {
6001
+ return null;
6002
+ }
6003
+ const literalDep = await loadTreesitterDepLiteral();
6004
+ if (literalDep === undefined) {
6005
+ return await resolve(cwd, spec);
6006
+ }
6007
+ const adapter = spec.load({ dep: literalDep, asset: null });
6008
+ let available;
6009
+ try {
6010
+ available = await adapter.isAvailable();
6011
+ } catch {
6012
+ warnCapabilityDegraded(spec.id, "adapter availability check threw");
6013
+ return null;
6014
+ }
6015
+ if (!available) {
6016
+ warnCapabilityDegraded(spec.id, "adapter reported unavailable");
6017
+ return null;
6018
+ }
6019
+ return adapter;
6020
+ } catch {
6021
+ return null;
6022
+ }
6023
+ }
5990
6024
 
5991
6025
  class TreesitterAdapter {
5992
6026
  cwd;
@@ -6102,6 +6136,8 @@ function compareCalls2(a, b) {
6102
6136
  return a.kind < b.kind ? -1 : a.kind > b.kind ? 1 : 0;
6103
6137
  }
6104
6138
  var init_adapter = __esm(() => {
6139
+ init_seam();
6140
+ init_warn_once();
6105
6141
  init_extract();
6106
6142
  init_grammars();
6107
6143
  });
@@ -6113,13 +6149,13 @@ __export(exports_enrich, {
6113
6149
  });
6114
6150
  import { mkdir as mkdir18, writeFile as writeFile20 } from "fs/promises";
6115
6151
  import path48 from "path";
6116
- async function enrichBuildWithSymbols(cwd, files, resolve = resolveCapability) {
6152
+ async function enrichBuildWithSymbols(cwd, files, resolve) {
6117
6153
  const config = await loadGdgraphConfig(cwd);
6118
- const spec = createTreesitterSpec(cwd, {
6154
+ const treesitterConfig = {
6119
6155
  languages: config.treesitter.languages,
6120
6156
  grammarsPath: config.treesitter.grammarsPath
6121
- });
6122
- const adapter = await resolve(cwd, spec);
6157
+ };
6158
+ const adapter = resolve ? await resolve(cwd, createTreesitterSpec(cwd, treesitterConfig)) : await resolveTreesitterCapability(cwd, treesitterConfig);
6123
6159
  if (!adapter) {
6124
6160
  return { enriched: false, symbols: 0, calls: 0 };
6125
6161
  }
@@ -6746,10 +6782,10 @@ var init_target = () => {};
6746
6782
  // src/gdgraph/query.ts
6747
6783
  var exports_query = {};
6748
6784
  __export(exports_query, {
6749
- loadGraph: () => loadGraph,
6750
- getOrphans: () => getOrphans,
6785
+ getAffected: () => getAffected,
6751
6786
  getCycles: () => getCycles,
6752
- getAffected: () => getAffected
6787
+ getOrphans: () => getOrphans,
6788
+ loadGraph: () => loadGraph
6753
6789
  });
6754
6790
  import { readFile as readFile24 } from "fs/promises";
6755
6791
  import path51 from "path";
@@ -7388,12 +7424,12 @@ var init_repomap = __esm(() => {
7388
7424
  // src/sync/provenance.ts
7389
7425
  var exports_provenance = {};
7390
7426
  __export(exports_provenance, {
7391
- recordProvenance: () => recordProvenance,
7392
- readProvenance: () => readProvenance,
7393
- provenancePath: () => provenancePath,
7394
- gitHead: () => gitHead,
7427
+ SYNCED_MODULES: () => SYNCED_MODULES,
7395
7428
  gitCmd: () => gitCmd,
7396
- SYNCED_MODULES: () => SYNCED_MODULES
7429
+ gitHead: () => gitHead,
7430
+ provenancePath: () => provenancePath,
7431
+ readProvenance: () => readProvenance,
7432
+ recordProvenance: () => recordProvenance
7397
7433
  });
7398
7434
  import { mkdir as mkdir21, readFile as readFile25, writeFile as writeFile23 } from "fs/promises";
7399
7435
  import path54 from "path";
@@ -7455,11 +7491,11 @@ var init_provenance = __esm(() => {
7455
7491
  // src/ctx/orient.ts
7456
7492
  var exports_orient = {};
7457
7493
  __export(exports_orient, {
7458
- wikiContext: () => wikiContext,
7459
- uncommittedCodeCount: () => uncommittedCodeCount,
7460
- metaprojectIndexContext: () => metaprojectIndexContext,
7494
+ buildOrientation: () => buildOrientation,
7461
7495
  graphContext: () => graphContext,
7462
- buildOrientation: () => buildOrientation
7496
+ metaprojectIndexContext: () => metaprojectIndexContext,
7497
+ uncommittedCodeCount: () => uncommittedCodeCount,
7498
+ wikiContext: () => wikiContext
7463
7499
  });
7464
7500
  import path55 from "path";
7465
7501
  import { spawn as spawn2 } from "child_process";
@@ -7666,9 +7702,9 @@ var init_orient = __esm(() => {
7666
7702
  // src/gdgraph/symbols-capability.ts
7667
7703
  var exports_symbols_capability = {};
7668
7704
  __export(exports_symbols_capability, {
7669
- setTreesitterEnabled: () => setTreesitterEnabled,
7705
+ TREESITTER_CAPABILITY: () => TREESITTER_CAPABILITY,
7670
7706
  isTreesitterEnabled: () => isTreesitterEnabled,
7671
- TREESITTER_CAPABILITY: () => TREESITTER_CAPABILITY
7707
+ setTreesitterEnabled: () => setTreesitterEnabled
7672
7708
  });
7673
7709
  function treesitterEntry(enabled) {
7674
7710
  return {
@@ -8081,9 +8117,9 @@ function titleSimilarity(a, b) {
8081
8117
  // src/wiki/collect.ts
8082
8118
  var exports_collect = {};
8083
8119
  __export(exports_collect, {
8084
- keyFilesForPage: () => keyFilesForPage,
8120
+ collectPages: () => collectPages,
8085
8121
  computeModuleKeyFiles: () => computeModuleKeyFiles,
8086
- collectPages: () => collectPages
8122
+ keyFilesForPage: () => keyFilesForPage
8087
8123
  });
8088
8124
  import { readFile as readFile29, readdir as readdir6 } from "fs/promises";
8089
8125
  import path58 from "path";
@@ -8581,18 +8617,18 @@ var init_backlinks = __esm(() => {
8581
8617
  // src/wiki/service.ts
8582
8618
  var exports_service = {};
8583
8619
  __export(exports_service, {
8584
- wikiValidate: () => wikiValidate,
8585
- wikiStatus: () => wikiStatus,
8586
- wikiPruneOrphans: () => wikiPruneOrphans,
8587
- wikiPagesForFile: () => wikiPagesForFile,
8588
- wikiGenerateIndex: () => wikiGenerateIndex,
8589
- wikiCreatePage: () => wikiCreatePage,
8590
- wikiCollect: () => wikiCollect,
8591
- wikiCheckLinks: () => wikiCheckLinks,
8592
- validModuleNames: () => validModuleNames,
8593
- moduleNameFromProjectPath: () => moduleNameFromProjectPath,
8620
+ createGdWikiService: () => createGdWikiService,
8594
8621
  extractModuleApi: () => extractModuleApi,
8595
- createGdWikiService: () => createGdWikiService
8622
+ moduleNameFromProjectPath: () => moduleNameFromProjectPath,
8623
+ validModuleNames: () => validModuleNames,
8624
+ wikiCheckLinks: () => wikiCheckLinks,
8625
+ wikiCollect: () => wikiCollect,
8626
+ wikiCreatePage: () => wikiCreatePage,
8627
+ wikiGenerateIndex: () => wikiGenerateIndex,
8628
+ wikiPagesForFile: () => wikiPagesForFile,
8629
+ wikiPruneOrphans: () => wikiPruneOrphans,
8630
+ wikiStatus: () => wikiStatus,
8631
+ wikiValidate: () => wikiValidate
8596
8632
  });
8597
8633
  import { spawn as spawn3 } from "child_process";
8598
8634
  import { mkdir as mkdir23, readFile as readFile30, readdir as readdir7, rm as rm3, writeFile as writeFile25 } from "fs/promises";
@@ -11454,9 +11490,396 @@ var init_anthropic_provider = __esm(() => {
11454
11490
  };
11455
11491
  });
11456
11492
 
11493
+ // src/harness/provider/compat/openai-compat-provider.ts
11494
+ function isPlainObject4(value) {
11495
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11496
+ }
11497
+ function asRecord3(value) {
11498
+ return isPlainObject4(value) ? value : {};
11499
+ }
11500
+ function asArray(value) {
11501
+ return Array.isArray(value) ? value : [];
11502
+ }
11503
+ function asString3(value) {
11504
+ return typeof value === "string" ? value : undefined;
11505
+ }
11506
+ function asNumber3(value) {
11507
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
11508
+ }
11509
+ function retryableFor2(kind, fallback) {
11510
+ const concrete = defaultRetryable(kind);
11511
+ return concrete === undefined ? fallback : concrete;
11512
+ }
11513
+ function mergeUsage2(promptTokens, completionTokens, totalTokens) {
11514
+ const usage = { exact: true };
11515
+ if (promptTokens !== undefined) {
11516
+ usage.inputTokens = promptTokens;
11517
+ }
11518
+ if (completionTokens !== undefined) {
11519
+ usage.outputTokens = completionTokens;
11520
+ }
11521
+ if (totalTokens !== undefined) {
11522
+ usage.totalTokens = totalTokens;
11523
+ } else if (promptTokens !== undefined || completionTokens !== undefined) {
11524
+ usage.totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0);
11525
+ }
11526
+ return usage;
11527
+ }
11528
+ function classifyHttpError2(status) {
11529
+ if (status >= 500) {
11530
+ return { kind: "unavailable", retryable: retryableFor2("unavailable", true), message: "" };
11531
+ }
11532
+ return { kind: "invalid_request", retryable: retryableFor2("invalid_request", false), message: "" };
11533
+ }
11534
+
11535
+ class OpenAiCompatEngine {
11536
+ deps;
11537
+ identity;
11538
+ constructor(deps, identity) {
11539
+ this.deps = deps;
11540
+ this.identity = identity;
11541
+ }
11542
+ get label() {
11543
+ return this.identity.providerLabel ?? this.identity.providerId;
11544
+ }
11545
+ describe() {
11546
+ const capabilities = {
11547
+ streaming: true,
11548
+ toolCalls: true,
11549
+ parallelToolCalls: true,
11550
+ structuredOutput: false,
11551
+ reasoningMetadata: false,
11552
+ promptCaching: false,
11553
+ vision: false,
11554
+ tokenCounting: false,
11555
+ modelListing: false
11556
+ };
11557
+ return {
11558
+ capabilities,
11559
+ descriptor: { providerId: this.identity.providerId, providerRevision: this.identity.providerRevision }
11560
+ };
11561
+ }
11562
+ descriptorDocument() {
11563
+ return {
11564
+ schemaVersion: 1,
11565
+ providerId: this.identity.providerId,
11566
+ providerRevision: this.identity.providerRevision,
11567
+ models: [{ modelId: this.identity.defaultModel.modelId, revision: this.identity.defaultModel.revision }],
11568
+ capabilities: {
11569
+ streaming: true,
11570
+ tools: true,
11571
+ parallelToolCalls: true,
11572
+ cancellation: true,
11573
+ structuredOutput: false
11574
+ },
11575
+ remoteState: { storage: false, retention: false, continuation: false }
11576
+ };
11577
+ }
11578
+ async* stream(request, opts) {
11579
+ let sequence = 0;
11580
+ const stamp = (body) => ({ ...body, sequence: sequence++, attemptId: opts.attemptId });
11581
+ const errorEvent = (error) => stamp({ kind: "provider_error", error });
11582
+ const grant = this.deps.grant;
11583
+ if (grant === undefined || grant.network !== true) {
11584
+ yield errorEvent({
11585
+ kind: "invalid_request",
11586
+ retryable: retryableFor2("invalid_request", false),
11587
+ message: `a network capability grant is required to reach the ${this.label} API`
11588
+ });
11589
+ return;
11590
+ }
11591
+ const baseUrl = grant.baseUrl ?? this.identity.defaultBaseUrl;
11592
+ let host;
11593
+ try {
11594
+ host = new URL(baseUrl).hostname;
11595
+ } catch {
11596
+ host = baseUrl;
11597
+ }
11598
+ const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true && isLoopbackHost(host);
11599
+ if (!permitted) {
11600
+ yield errorEvent({
11601
+ kind: "invalid_request",
11602
+ retryable: retryableFor2("invalid_request", false),
11603
+ message: `egress to a private/loopback/link-local/metadata host is denied: ${host}`
11604
+ });
11605
+ return;
11606
+ }
11607
+ const url = `${baseUrl.replace(/\/+$/, "")}${grant.chatPath ?? "/v1/chat/completions"}`;
11608
+ const messages = [];
11609
+ if (request.systemInstruction.length > 0) {
11610
+ messages.push({ role: "system", content: request.systemInstruction });
11611
+ }
11612
+ for (const linked of linkToolCalls(request.messages)) {
11613
+ const message = linked.message;
11614
+ if (message.role === "tool") {
11615
+ if (linked.linkedToolCallId !== undefined) {
11616
+ messages.push({ role: "tool", tool_call_id: linked.linkedToolCallId, content: message.content });
11617
+ continue;
11618
+ }
11619
+ messages.push({ role: "user", content: `Tool result:
11620
+ ${message.content}` });
11621
+ continue;
11622
+ }
11623
+ if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
11624
+ continue;
11625
+ }
11626
+ if (message.role === "assistant" && linked.linkedCalls.length > 0) {
11627
+ messages.push({
11628
+ role: "assistant",
11629
+ content: message.content,
11630
+ tool_calls: linked.linkedCalls.map((call) => ({
11631
+ id: call.id,
11632
+ type: "function",
11633
+ function: { name: call.name, arguments: call.arguments }
11634
+ }))
11635
+ });
11636
+ continue;
11637
+ }
11638
+ messages.push({ role: message.role, content: message.content });
11639
+ }
11640
+ const payload = {
11641
+ model: request.modelId,
11642
+ stream: true,
11643
+ messages,
11644
+ ...request.tools !== undefined ? {
11645
+ tools: request.tools.map((tool) => ({
11646
+ type: "function",
11647
+ function: {
11648
+ name: tool.name,
11649
+ ...tool.description !== undefined ? { description: tool.description } : {},
11650
+ parameters: tool.inputSchema
11651
+ }
11652
+ }))
11653
+ } : {}
11654
+ };
11655
+ const headers = { "content-type": "application/json" };
11656
+ if (grant.apiKey !== undefined && grant.apiKey.length > 0) {
11657
+ headers.authorization = `Bearer ${grant.apiKey}`;
11658
+ }
11659
+ if (grant.headers !== undefined) {
11660
+ Object.assign(headers, grant.headers);
11661
+ }
11662
+ const init = {
11663
+ method: "POST",
11664
+ headers,
11665
+ body: JSON.stringify(payload),
11666
+ ...opts.signal !== undefined ? { signal: opts.signal } : {}
11667
+ };
11668
+ let response;
11669
+ try {
11670
+ response = await this.deps.fetch(url, init);
11671
+ } catch (cause) {
11672
+ if (opts.signal?.aborted === true) {
11673
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11674
+ return;
11675
+ }
11676
+ yield errorEvent({
11677
+ kind: "unavailable",
11678
+ retryable: retryableFor2("unavailable", true),
11679
+ message: `network request to the ${this.label} API failed: ${String(cause)}`
11680
+ });
11681
+ return;
11682
+ }
11683
+ if (!response.ok) {
11684
+ const error = classifyHttpError2(response.status);
11685
+ let providerMessage = `${this.label} API returned HTTP ${response.status}`;
11686
+ try {
11687
+ const parsed = asRecord3(JSON.parse(await response.text()));
11688
+ const detail = asString3(asRecord3(parsed.error).message);
11689
+ if (detail !== undefined && detail.length > 0) {
11690
+ providerMessage = detail;
11691
+ }
11692
+ } catch {}
11693
+ error.message = providerMessage;
11694
+ yield stamp({ kind: "provider_error", error });
11695
+ return;
11696
+ }
11697
+ let bodyText;
11698
+ try {
11699
+ bodyText = await response.text();
11700
+ } catch (cause) {
11701
+ const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
11702
+ if (aborted) {
11703
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11704
+ return;
11705
+ }
11706
+ yield errorEvent({
11707
+ kind: "malformed",
11708
+ retryable: retryableFor2("malformed", false),
11709
+ message: `${this.label} SSE body read failed: ${String(cause)}`
11710
+ });
11711
+ return;
11712
+ }
11713
+ if (bodyText.length === 0) {
11714
+ yield errorEvent({
11715
+ kind: "malformed",
11716
+ retryable: retryableFor2("malformed", false),
11717
+ message: "empty response body"
11718
+ });
11719
+ return;
11720
+ }
11721
+ const parser = new AnthropicSSEParser;
11722
+ const records = parser.push(bodyText);
11723
+ const torn = parser.flush();
11724
+ const bodies = [];
11725
+ let sawStart = false;
11726
+ let sawFinish = false;
11727
+ let sawDone = false;
11728
+ let malformed;
11729
+ const pendingTools = new Map;
11730
+ const toolCallKey = (toolCall) => {
11731
+ const index = asNumber3(toolCall.index);
11732
+ if (index !== undefined) {
11733
+ return `idx:${index}`;
11734
+ }
11735
+ const id = asString3(toolCall.id);
11736
+ if (id !== undefined && id.length > 0) {
11737
+ return `id:${id}`;
11738
+ }
11739
+ return "idx:0";
11740
+ };
11741
+ const flushPendingToolEnds = () => {
11742
+ for (const acc of pendingTools.values()) {
11743
+ if (acc.ended) {
11744
+ continue;
11745
+ }
11746
+ if (!acc.started) {
11747
+ const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11748
+ if (acc.name.length > 0) {
11749
+ startBody.toolName = acc.name;
11750
+ }
11751
+ bodies.push(startBody);
11752
+ acc.started = true;
11753
+ }
11754
+ bodies.push({ kind: "tool_call_end", toolCallId: acc.id, input: acc.arguments });
11755
+ acc.ended = true;
11756
+ }
11757
+ pendingTools.clear();
11758
+ };
11759
+ for (const record of records) {
11760
+ const trimmed = record.data.trim();
11761
+ if (trimmed === "[DONE]") {
11762
+ sawDone = true;
11763
+ flushPendingToolEnds();
11764
+ continue;
11765
+ }
11766
+ if (!sawStart) {
11767
+ sawStart = true;
11768
+ bodies.push({ kind: "model_start" });
11769
+ }
11770
+ let parsed;
11771
+ try {
11772
+ parsed = JSON.parse(record.data);
11773
+ } catch {
11774
+ malformed = {
11775
+ kind: "malformed",
11776
+ retryable: retryableFor2("malformed", false),
11777
+ message: `${this.label} SSE data line was not valid JSON`
11778
+ };
11779
+ break;
11780
+ }
11781
+ const data = asRecord3(parsed);
11782
+ if (data.usage !== undefined) {
11783
+ const usage = asRecord3(data.usage);
11784
+ bodies.push({
11785
+ kind: "usage_update",
11786
+ usage: mergeUsage2(asNumber3(usage.prompt_tokens), asNumber3(usage.completion_tokens), asNumber3(usage.total_tokens))
11787
+ });
11788
+ }
11789
+ const choice0 = asRecord3(asArray(data.choices)[0]);
11790
+ const delta = asRecord3(choice0.delta);
11791
+ const reasoning = asString3(delta.reasoning) ?? asString3(delta.reasoning_content);
11792
+ if (reasoning !== undefined && reasoning.length > 0) {
11793
+ bodies.push({ kind: "reasoning_delta", text: reasoning });
11794
+ }
11795
+ const content = asString3(delta.content);
11796
+ if (content !== undefined && content.length > 0) {
11797
+ bodies.push({ kind: "text_delta", text: content });
11798
+ }
11799
+ for (const rawToolCall of asArray(delta.tool_calls)) {
11800
+ const toolCall = asRecord3(rawToolCall);
11801
+ const fn = asRecord3(toolCall.function);
11802
+ const toolCallId = asString3(toolCall.id);
11803
+ const toolName = asString3(fn.name);
11804
+ const argumentsFragment = asString3(fn.arguments) ?? "";
11805
+ const key = toolCallKey(toolCall);
11806
+ let acc = pendingTools.get(key);
11807
+ if (acc === undefined) {
11808
+ acc = {
11809
+ id: toolCallId ?? `call_${key.replace(/[^a-zA-Z0-9_:-]/g, "_")}`,
11810
+ name: toolName ?? "",
11811
+ arguments: "",
11812
+ started: false,
11813
+ ended: false
11814
+ };
11815
+ pendingTools.set(key, acc);
11816
+ }
11817
+ if (toolCallId !== undefined && toolCallId.length > 0) {
11818
+ acc.id = toolCallId;
11819
+ }
11820
+ if (toolName !== undefined && toolName.length > 0) {
11821
+ acc.name = toolName;
11822
+ }
11823
+ if (!acc.started) {
11824
+ const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11825
+ if (acc.name.length > 0) {
11826
+ startBody.toolName = acc.name;
11827
+ }
11828
+ bodies.push(startBody);
11829
+ acc.started = true;
11830
+ }
11831
+ if (argumentsFragment.length > 0) {
11832
+ acc.arguments += argumentsFragment;
11833
+ bodies.push({
11834
+ kind: "tool_call_delta",
11835
+ toolCallId: acc.id,
11836
+ inputDelta: argumentsFragment
11837
+ });
11838
+ }
11839
+ }
11840
+ const finishReason = asString3(choice0.finish_reason);
11841
+ if (finishReason !== undefined && finishReason.length > 0) {
11842
+ sawFinish = true;
11843
+ flushPendingToolEnds();
11844
+ }
11845
+ }
11846
+ if (malformed === undefined && torn.length > 0) {
11847
+ malformed = {
11848
+ kind: "malformed",
11849
+ retryable: retryableFor2("malformed", false),
11850
+ message: `${this.label} SSE stream ended mid-record (torn stream)`
11851
+ };
11852
+ }
11853
+ if (malformed === undefined) {
11854
+ flushPendingToolEnds();
11855
+ }
11856
+ if (malformed === undefined && sawStart && (sawDone || sawFinish)) {
11857
+ bodies.push({ kind: "model_end" });
11858
+ }
11859
+ for (const body of bodies) {
11860
+ if (opts.signal?.aborted === true) {
11861
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11862
+ return;
11863
+ }
11864
+ yield stamp(body);
11865
+ }
11866
+ if (opts.signal?.aborted === true) {
11867
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
11868
+ return;
11869
+ }
11870
+ if (malformed !== undefined) {
11871
+ yield stamp({ kind: "provider_error", error: malformed });
11872
+ }
11873
+ }
11874
+ }
11875
+ var init_openai_compat_provider = __esm(() => {
11876
+ init_guard2();
11877
+ init_provider_port();
11878
+ });
11879
+
11457
11880
  // src/harness/provider/fake-provider.ts
11458
11881
  import { createHash as createHash5 } from "crypto";
11459
- function isPlainObject4(value) {
11882
+ function isPlainObject5(value) {
11460
11883
  return typeof value === "object" && value !== null && !Array.isArray(value);
11461
11884
  }
11462
11885
  function canonicalize(value) {
@@ -11483,7 +11906,7 @@ function requestHashOf(request) {
11483
11906
  }
11484
11907
  function extractUnknownExtensions(payload) {
11485
11908
  const provider = payload.provider;
11486
- if (!isPlainObject4(provider)) {
11909
+ if (!isPlainObject5(provider)) {
11487
11910
  return;
11488
11911
  }
11489
11912
  const extensions = {};
@@ -11561,7 +11984,7 @@ class FakeProvider {
11561
11984
  }
11562
11985
  case "tool_call": {
11563
11986
  const input2 = payload.input;
11564
- if (!isPlainObject4(input2)) {
11987
+ if (!isPlainObject5(input2)) {
11565
11988
  yield emit({
11566
11989
  kind: "provider_error",
11567
11990
  error: {
@@ -11587,7 +12010,7 @@ class FakeProvider {
11587
12010
  }
11588
12011
  case "finish": {
11589
12012
  const usage = payload.usage;
11590
- if (isPlainObject4(usage)) {
12013
+ if (isPlainObject5(usage)) {
11591
12014
  yield emit({ kind: "usage_update", usage: mapUsage(usage) });
11592
12015
  }
11593
12016
  const body = { kind: "model_end" };
@@ -11615,49 +12038,143 @@ var init_fake_provider = __esm(() => {
11615
12038
  init_provider_port();
11616
12039
  });
11617
12040
 
11618
- // src/harness/provider/ollama/ollama-provider.ts
11619
- function isPlainObject5(value) {
12041
+ // src/harness/provider/gemini/gemini-provider.ts
12042
+ function isPlainObject6(value) {
11620
12043
  return typeof value === "object" && value !== null && !Array.isArray(value);
11621
12044
  }
11622
- function asRecord3(value) {
11623
- return isPlainObject5(value) ? value : {};
12045
+ function asRecord4(value) {
12046
+ return isPlainObject6(value) ? value : {};
11624
12047
  }
11625
- function asArray(value) {
12048
+ function asArray2(value) {
11626
12049
  return Array.isArray(value) ? value : [];
11627
12050
  }
11628
- function asString3(value) {
12051
+ function asString4(value) {
11629
12052
  return typeof value === "string" ? value : undefined;
11630
12053
  }
11631
- function asNumber3(value) {
12054
+ function asNumber4(value) {
11632
12055
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
11633
12056
  }
11634
- function retryableFor2(kind, fallback) {
12057
+ function asBoolean(value) {
12058
+ return value === true;
12059
+ }
12060
+ function toGeminiContents(messages) {
12061
+ const out = [];
12062
+ for (const linked of linkToolCalls(messages)) {
12063
+ const message = linked.message;
12064
+ if (message.role === "system") {
12065
+ continue;
12066
+ }
12067
+ if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
12068
+ continue;
12069
+ }
12070
+ if (message.role === "tool" && linked.linkedToolCallId !== undefined) {
12071
+ const call = linked.message.toolCallId;
12072
+ out.push({
12073
+ role: "user",
12074
+ parts: [
12075
+ {
12076
+ functionResponse: {
12077
+ name: findToolName(messages, linked.linkedToolCallId) ?? "",
12078
+ id: call,
12079
+ response: parseFunctionResponse(message.content)
12080
+ }
12081
+ }
12082
+ ]
12083
+ });
12084
+ continue;
12085
+ }
12086
+ if (message.role === "assistant" && linked.linkedCalls.length > 0) {
12087
+ const parts = [];
12088
+ if (message.content.length > 0) {
12089
+ parts.push({ text: message.content });
12090
+ }
12091
+ for (const call of linked.linkedCalls) {
12092
+ parts.push({
12093
+ functionCall: { name: call.name, id: call.id, args: parseToolInput2(call.arguments) }
12094
+ });
12095
+ }
12096
+ out.push({ role: "model", parts });
12097
+ continue;
12098
+ }
12099
+ out.push({
12100
+ role: message.role === "assistant" ? "model" : "user",
12101
+ parts: [{ text: message.content }]
12102
+ });
12103
+ }
12104
+ return out;
12105
+ }
12106
+ function findToolName(messages, toolCallId) {
12107
+ for (const message of messages) {
12108
+ if (message.role !== "assistant" || !Array.isArray(message.toolCalls)) {
12109
+ continue;
12110
+ }
12111
+ const match = message.toolCalls.find((call) => call.id === toolCallId);
12112
+ if (match !== undefined) {
12113
+ return match.name;
12114
+ }
12115
+ }
12116
+ return;
12117
+ }
12118
+ function parseFunctionResponse(rawContent) {
12119
+ if (rawContent.trim().length === 0) {
12120
+ return {};
12121
+ }
12122
+ try {
12123
+ const parsed = JSON.parse(rawContent);
12124
+ return isPlainObject6(parsed) ? parsed : { result: parsed };
12125
+ } catch {
12126
+ return { result: rawContent };
12127
+ }
12128
+ }
12129
+ function parseToolInput2(rawArguments) {
12130
+ if (rawArguments.trim().length === 0) {
12131
+ return {};
12132
+ }
12133
+ try {
12134
+ return asRecord4(JSON.parse(rawArguments));
12135
+ } catch {
12136
+ return {};
12137
+ }
12138
+ }
12139
+ function retryableFor3(kind, fallback) {
11635
12140
  const concrete = defaultRetryable(kind);
11636
12141
  return concrete === undefined ? fallback : concrete;
11637
12142
  }
11638
- function mergeUsage2(promptTokens, completionTokens, totalTokens) {
12143
+ function mergeUsage3(promptTokens, candidatesTokens, totalTokens) {
11639
12144
  const usage = { exact: true };
11640
12145
  if (promptTokens !== undefined) {
11641
12146
  usage.inputTokens = promptTokens;
11642
12147
  }
11643
- if (completionTokens !== undefined) {
11644
- usage.outputTokens = completionTokens;
12148
+ if (candidatesTokens !== undefined) {
12149
+ usage.outputTokens = candidatesTokens;
11645
12150
  }
11646
12151
  if (totalTokens !== undefined) {
11647
12152
  usage.totalTokens = totalTokens;
11648
- } else if (promptTokens !== undefined || completionTokens !== undefined) {
11649
- usage.totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0);
12153
+ } else if (promptTokens !== undefined || candidatesTokens !== undefined) {
12154
+ usage.totalTokens = (promptTokens ?? 0) + (candidatesTokens ?? 0);
11650
12155
  }
11651
12156
  return usage;
11652
12157
  }
11653
- function classifyHttpError2(status) {
11654
- if (status >= 500) {
11655
- return { kind: "unavailable", retryable: retryableFor2("unavailable", true), message: "" };
12158
+ function classifyGeminiError(httpStatus, rpcStatus) {
12159
+ if (rpcStatus === "UNAUTHENTICATED" || httpStatus === 401 || httpStatus === 403) {
12160
+ return { kind: "authentication", retryable: retryableFor3("authentication", false), message: "" };
11656
12161
  }
11657
- return { kind: "invalid_request", retryable: retryableFor2("invalid_request", false), message: "" };
12162
+ if (rpcStatus === "RESOURCE_EXHAUSTED" || httpStatus === 429) {
12163
+ return { kind: "rate_limit", retryable: retryableFor3("rate_limit", true), message: "" };
12164
+ }
12165
+ if (rpcStatus === "UNAVAILABLE" || httpStatus >= 500) {
12166
+ return { kind: "unavailable", retryable: retryableFor3("unavailable", true), message: "" };
12167
+ }
12168
+ if (rpcStatus === "INVALID_ARGUMENT" || httpStatus === 400) {
12169
+ return { kind: "invalid_request", retryable: retryableFor3("invalid_request", false), message: "" };
12170
+ }
12171
+ if (httpStatus >= 400) {
12172
+ return { kind: "invalid_request", retryable: retryableFor3("invalid_request", false), message: "" };
12173
+ }
12174
+ return { kind: "unknown", retryable: retryableFor3("unknown", false), message: "" };
11658
12175
  }
11659
12176
 
11660
- class OllamaProvider {
12177
+ class GeminiProvider {
11661
12178
  deps;
11662
12179
  constructor(deps) {
11663
12180
  this.deps = deps;
@@ -11667,22 +12184,22 @@ class OllamaProvider {
11667
12184
  streaming: true,
11668
12185
  toolCalls: true,
11669
12186
  parallelToolCalls: true,
11670
- structuredOutput: false,
11671
- reasoningMetadata: false,
11672
- promptCaching: false,
11673
- vision: false,
12187
+ structuredOutput: true,
12188
+ reasoningMetadata: true,
12189
+ promptCaching: true,
12190
+ vision: true,
11674
12191
  tokenCounting: false,
11675
12192
  modelListing: false
11676
12193
  };
11677
12194
  return {
11678
12195
  capabilities,
11679
- descriptor: { providerId: "ollama", providerRevision: PROVIDER_REVISION2 }
12196
+ descriptor: { providerId: "gemini", providerRevision: PROVIDER_REVISION2 }
11680
12197
  };
11681
12198
  }
11682
12199
  descriptorDocument() {
11683
12200
  return {
11684
12201
  schemaVersion: 1,
11685
- providerId: "ollama",
12202
+ providerId: "gemini",
11686
12203
  providerRevision: PROVIDER_REVISION2,
11687
12204
  models: [{ modelId: DEFAULT_MODEL2.modelId, revision: DEFAULT_MODEL2.revision }],
11688
12205
  capabilities: {
@@ -11690,7 +12207,7 @@ class OllamaProvider {
11690
12207
  tools: true,
11691
12208
  parallelToolCalls: true,
11692
12209
  cancellation: true,
11693
- structuredOutput: false
12210
+ structuredOutput: true
11694
12211
  },
11695
12212
  remoteState: { storage: false, retention: false, continuation: false }
11696
12213
  };
@@ -11700,11 +12217,12 @@ class OllamaProvider {
11700
12217
  const stamp = (body) => ({ ...body, sequence: sequence++, attemptId: opts.attemptId });
11701
12218
  const errorEvent = (error) => stamp({ kind: "provider_error", error });
11702
12219
  const grant = this.deps.grant;
11703
- if (grant === undefined || grant.network !== true) {
12220
+ const redact = (message) => grant !== undefined && grant.apiKey.length > 0 ? message.split(grant.apiKey).join("[redacted]") : message;
12221
+ if (grant === undefined || grant.network !== true || typeof grant.apiKey !== "string" || grant.apiKey.length === 0) {
11704
12222
  yield errorEvent({
11705
- kind: "invalid_request",
11706
- retryable: retryableFor2("invalid_request", false),
11707
- message: "a network capability grant is required to reach the Ollama API"
12223
+ kind: "authentication",
12224
+ retryable: retryableFor3("authentication", false),
12225
+ message: "network capability grant with an apiKey is required to reach the Gemini API"
11708
12226
  });
11709
12227
  return;
11710
12228
  }
@@ -11715,70 +12233,459 @@ class OllamaProvider {
11715
12233
  } catch {
11716
12234
  host = baseUrl;
11717
12235
  }
11718
- const permitted = !isPrivateEgressHost(host) || grant.allowLoopback === true && isLoopbackHost(host);
11719
- if (!permitted) {
12236
+ if (isPrivateEgressHost(host)) {
11720
12237
  yield errorEvent({
11721
12238
  kind: "invalid_request",
11722
- retryable: retryableFor2("invalid_request", false),
11723
- message: `egress to a private/loopback/link-local/metadata host is denied: ${host}`
12239
+ retryable: retryableFor3("invalid_request", false),
12240
+ message: redact(`egress to a private/loopback/link-local/metadata host is denied: ${host}`)
11724
12241
  });
11725
12242
  return;
11726
12243
  }
11727
- const url = `${baseUrl.replace(/\/+$/, "")}${grant.chatPath ?? "/v1/chat/completions"}`;
11728
- const messages = [];
11729
- if (request.systemInstruction.length > 0) {
11730
- messages.push({ role: "system", content: request.systemInstruction });
12244
+ const url = `${baseUrl.replace(/\/+$/, "")}/v1beta/models/${encodeURIComponent(request.modelId)}:streamGenerateContent?alt=sse`;
12245
+ const headers = {
12246
+ "x-goog-api-key": grant.apiKey,
12247
+ "content-type": "application/json"
12248
+ };
12249
+ const payload = {
12250
+ contents: toGeminiContents(request.messages),
12251
+ ...request.systemInstruction.length > 0 ? { systemInstruction: { parts: [{ text: request.systemInstruction }] } } : {},
12252
+ ...request.tools !== undefined && request.tools.length > 0 ? {
12253
+ tools: [
12254
+ {
12255
+ functionDeclarations: request.tools.map((tool) => ({
12256
+ name: tool.name,
12257
+ ...tool.description !== undefined ? { description: tool.description } : {},
12258
+ parameters: tool.inputSchema
12259
+ }))
12260
+ }
12261
+ ]
12262
+ } : {},
12263
+ generationConfig: {
12264
+ maxOutputTokens: request.budget.maxOutputTokens,
12265
+ ...request.options?.temperature !== undefined ? { temperature: request.options.temperature } : {}
12266
+ }
12267
+ };
12268
+ const init = {
12269
+ method: "POST",
12270
+ headers,
12271
+ body: JSON.stringify(payload),
12272
+ ...opts.signal !== undefined ? { signal: opts.signal } : {}
12273
+ };
12274
+ let response;
12275
+ try {
12276
+ response = await this.deps.fetch(url, init);
12277
+ } catch (cause) {
12278
+ if (opts.signal?.aborted === true) {
12279
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12280
+ return;
12281
+ }
12282
+ yield errorEvent({
12283
+ kind: "unavailable",
12284
+ retryable: retryableFor3("unavailable", true),
12285
+ message: redact(`network request to the Gemini API failed: ${String(cause)}`)
12286
+ });
12287
+ return;
11731
12288
  }
11732
- for (const linked of linkToolCalls(request.messages)) {
11733
- const message = linked.message;
11734
- if (message.role === "tool") {
11735
- if (linked.linkedToolCallId !== undefined) {
11736
- messages.push({ role: "tool", tool_call_id: linked.linkedToolCallId, content: message.content });
12289
+ if (!response.ok) {
12290
+ let error;
12291
+ let providerMessage = `Gemini API returned HTTP ${response.status}`;
12292
+ try {
12293
+ const parsed = asRecord4(JSON.parse(await response.text()));
12294
+ const envelope = asRecord4(parsed.error);
12295
+ const rpcStatus = asString4(envelope.status);
12296
+ const httpCode = asNumber4(envelope.code) ?? response.status;
12297
+ error = classifyGeminiError(httpCode, rpcStatus);
12298
+ const detail = asString4(envelope.message);
12299
+ if (detail !== undefined && detail.length > 0) {
12300
+ providerMessage = detail;
12301
+ }
12302
+ } catch {
12303
+ error = classifyGeminiError(response.status, undefined);
12304
+ }
12305
+ error.message = redact(providerMessage);
12306
+ yield stamp({ kind: "provider_error", error });
12307
+ return;
12308
+ }
12309
+ let bodyText;
12310
+ try {
12311
+ bodyText = await response.text();
12312
+ } catch (cause) {
12313
+ const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
12314
+ if (aborted) {
12315
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12316
+ return;
12317
+ }
12318
+ yield errorEvent({
12319
+ kind: "malformed",
12320
+ retryable: retryableFor3("malformed", false),
12321
+ message: redact(`Gemini SSE body read failed: ${String(cause)}`)
12322
+ });
12323
+ return;
12324
+ }
12325
+ if (bodyText.length === 0) {
12326
+ yield errorEvent({
12327
+ kind: "malformed",
12328
+ retryable: retryableFor3("malformed", false),
12329
+ message: redact("empty response body")
12330
+ });
12331
+ return;
12332
+ }
12333
+ const parser = new AnthropicSSEParser;
12334
+ const records = parser.push(bodyText);
12335
+ const torn = parser.flush();
12336
+ const bodies = [];
12337
+ let sawFirstChunk = false;
12338
+ let sawFinish = false;
12339
+ let malformed;
12340
+ let promptTokens;
12341
+ let candidatesTokens;
12342
+ let totalTokens;
12343
+ let cachedContentTokens;
12344
+ let thoughtsTokens;
12345
+ for (const record of records) {
12346
+ let parsed;
12347
+ try {
12348
+ parsed = JSON.parse(record.data);
12349
+ } catch {
12350
+ malformed = {
12351
+ kind: "malformed",
12352
+ retryable: retryableFor3("malformed", false),
12353
+ message: redact("Gemini SSE data line was not valid JSON")
12354
+ };
12355
+ break;
12356
+ }
12357
+ const chunk = asRecord4(parsed);
12358
+ if (!sawFirstChunk) {
12359
+ sawFirstChunk = true;
12360
+ bodies.push({ kind: "model_start" });
12361
+ }
12362
+ const candidates = asArray2(chunk.candidates);
12363
+ const firstCandidate = asRecord4(candidates[0]);
12364
+ const content = asRecord4(firstCandidate.content);
12365
+ const parts = asArray2(content.parts);
12366
+ for (const rawPart of parts) {
12367
+ const part = asRecord4(rawPart);
12368
+ const text = asString4(part.text);
12369
+ if (text !== undefined) {
12370
+ if (asBoolean(part.thought)) {
12371
+ bodies.push({ kind: "reasoning_delta", text });
12372
+ } else {
12373
+ bodies.push({ kind: "text_delta", text });
12374
+ }
11737
12375
  continue;
11738
12376
  }
11739
- messages.push({ role: "user", content: `Tool result:
11740
- ${message.content}` });
11741
- continue;
12377
+ const functionCall = asRecord4(part.functionCall);
12378
+ const callName = asString4(functionCall.name);
12379
+ if (callName !== undefined) {
12380
+ const callId = asString4(functionCall.id) ?? callName;
12381
+ const argsInput = JSON.stringify(asRecord4(functionCall.args));
12382
+ bodies.push({ kind: "tool_call_start", toolCallId: callId, toolName: callName });
12383
+ bodies.push({ kind: "tool_call_end", toolCallId: callId, input: argsInput });
12384
+ }
11742
12385
  }
11743
- if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
12386
+ const usageMetadata = asRecord4(chunk.usageMetadata);
12387
+ if (Object.keys(usageMetadata).length > 0) {
12388
+ promptTokens = asNumber4(usageMetadata.promptTokenCount) ?? promptTokens;
12389
+ candidatesTokens = asNumber4(usageMetadata.candidatesTokenCount) ?? candidatesTokens;
12390
+ totalTokens = asNumber4(usageMetadata.totalTokenCount) ?? totalTokens;
12391
+ cachedContentTokens = asNumber4(usageMetadata.cachedContentTokenCount) ?? cachedContentTokens;
12392
+ thoughtsTokens = asNumber4(usageMetadata.thoughtsTokenCount) ?? thoughtsTokens;
12393
+ }
12394
+ const finishReason = asString4(firstCandidate.finishReason);
12395
+ if (finishReason !== undefined && finishReason.length > 0) {
12396
+ sawFinish = true;
12397
+ }
12398
+ }
12399
+ if (promptTokens !== undefined || candidatesTokens !== undefined || totalTokens !== undefined) {
12400
+ const usage = mergeUsage3(promptTokens, candidatesTokens, totalTokens);
12401
+ const unknownExtensions = {};
12402
+ if (cachedContentTokens !== undefined) {
12403
+ unknownExtensions["gemini.cached_content_tokens"] = cachedContentTokens;
12404
+ }
12405
+ if (thoughtsTokens !== undefined) {
12406
+ unknownExtensions["gemini.thoughts_tokens"] = thoughtsTokens;
12407
+ }
12408
+ bodies.push({
12409
+ kind: "usage_update",
12410
+ usage,
12411
+ ...Object.keys(unknownExtensions).length > 0 ? { unknownExtensions } : {}
12412
+ });
12413
+ }
12414
+ if (sawFinish) {
12415
+ bodies.push({ kind: "model_end" });
12416
+ }
12417
+ if (malformed === undefined) {
12418
+ if (torn.length > 0) {
12419
+ malformed = {
12420
+ kind: "malformed",
12421
+ retryable: retryableFor3("malformed", false),
12422
+ message: redact("Gemini SSE stream ended mid-record (torn stream)")
12423
+ };
12424
+ } else if (sawFirstChunk && !sawFinish) {
12425
+ malformed = {
12426
+ kind: "malformed",
12427
+ retryable: retryableFor3("malformed", false),
12428
+ message: redact("Gemini SSE stream ended before a finishReason was reported (truncated stream)")
12429
+ };
12430
+ }
12431
+ }
12432
+ for (const body of bodies) {
12433
+ if (opts.signal?.aborted === true) {
12434
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12435
+ return;
12436
+ }
12437
+ yield stamp(body);
12438
+ }
12439
+ if (opts.signal?.aborted === true) {
12440
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor3("cancelled", false), message: "attempt cancelled" });
12441
+ return;
12442
+ }
12443
+ if (malformed !== undefined) {
12444
+ yield stamp({ kind: "provider_error", error: malformed });
12445
+ }
12446
+ }
12447
+ }
12448
+ var DEFAULT_BASE_URL2 = "https://generativelanguage.googleapis.com", PROVIDER_REVISION2 = "gemini-2026-08-20", DEFAULT_MODEL2;
12449
+ var init_gemini_provider = __esm(() => {
12450
+ init_guard2();
12451
+ init_provider_port();
12452
+ DEFAULT_MODEL2 = {
12453
+ modelId: "gemini-2.5-flash",
12454
+ revision: "2.5"
12455
+ };
12456
+ });
12457
+
12458
+ // src/harness/provider/ollama/ollama-provider.ts
12459
+ class OllamaProvider {
12460
+ engine;
12461
+ constructor(deps) {
12462
+ this.engine = new OpenAiCompatEngine(deps, {
12463
+ defaultBaseUrl: DEFAULT_BASE_URL3,
12464
+ providerRevision: PROVIDER_REVISION3,
12465
+ providerId: "ollama",
12466
+ providerLabel: "Ollama",
12467
+ defaultModel: DEFAULT_MODEL3
12468
+ });
12469
+ }
12470
+ describe() {
12471
+ return this.engine.describe();
12472
+ }
12473
+ descriptorDocument() {
12474
+ return this.engine.descriptorDocument();
12475
+ }
12476
+ stream(request, opts) {
12477
+ return this.engine.stream(request, opts);
12478
+ }
12479
+ }
12480
+ var DEFAULT_BASE_URL3 = "http://localhost:11434", PROVIDER_REVISION3 = "ollama-2024-10-22", DEFAULT_MODEL3;
12481
+ var init_ollama_provider = __esm(() => {
12482
+ init_openai_compat_provider();
12483
+ DEFAULT_MODEL3 = {
12484
+ modelId: "llama3.1:latest",
12485
+ revision: "latest"
12486
+ };
12487
+ });
12488
+
12489
+ // src/harness/provider/openai/openai-provider.ts
12490
+ function isPlainObject7(value) {
12491
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12492
+ }
12493
+ function asRecord5(value) {
12494
+ return isPlainObject7(value) ? value : {};
12495
+ }
12496
+ function asString5(value) {
12497
+ return typeof value === "string" ? value : undefined;
12498
+ }
12499
+ function asNumber5(value) {
12500
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
12501
+ }
12502
+ function toResponsesInput(messages) {
12503
+ const out = [];
12504
+ for (const linked of linkToolCalls(messages)) {
12505
+ const message = linked.message;
12506
+ if (message.role === "tool") {
12507
+ if (linked.linkedToolCallId !== undefined) {
12508
+ out.push({ type: "function_call_output", call_id: linked.linkedToolCallId, output: message.content });
11744
12509
  continue;
11745
12510
  }
11746
- if (message.role === "assistant" && linked.linkedCalls.length > 0) {
11747
- messages.push({
12511
+ out.push({ type: "message", role: "user", content: [{ type: "input_text", text: message.content }] });
12512
+ continue;
12513
+ }
12514
+ if (message.role === "assistant" && message.content.length === 0 && linked.linkedCalls.length === 0) {
12515
+ continue;
12516
+ }
12517
+ if (message.role === "assistant" && linked.linkedCalls.length > 0) {
12518
+ if (message.content.length > 0) {
12519
+ out.push({
12520
+ type: "message",
11748
12521
  role: "assistant",
11749
- content: message.content,
11750
- tool_calls: linked.linkedCalls.map((call) => ({
11751
- id: call.id,
11752
- type: "function",
11753
- function: { name: call.name, arguments: call.arguments }
11754
- }))
12522
+ content: [{ type: "output_text", text: message.content }]
11755
12523
  });
11756
- continue;
11757
12524
  }
11758
- messages.push({ role: message.role, content: message.content });
12525
+ for (const call of linked.linkedCalls) {
12526
+ out.push({ type: "function_call", call_id: call.id, name: call.name, arguments: call.arguments });
12527
+ }
12528
+ continue;
12529
+ }
12530
+ out.push({
12531
+ type: "message",
12532
+ role: message.role === "assistant" ? "assistant" : "user",
12533
+ content: [{ type: message.role === "assistant" ? "output_text" : "input_text", text: message.content }]
12534
+ });
12535
+ }
12536
+ return out;
12537
+ }
12538
+ function retryableFor4(kind, fallback) {
12539
+ const concrete = defaultRetryable(kind);
12540
+ return concrete === undefined ? fallback : concrete;
12541
+ }
12542
+ function mergeUsage4(inputTokens, outputTokens, totalTokens) {
12543
+ const usage = { exact: true };
12544
+ if (inputTokens !== undefined) {
12545
+ usage.inputTokens = inputTokens;
12546
+ }
12547
+ if (outputTokens !== undefined) {
12548
+ usage.outputTokens = outputTokens;
12549
+ }
12550
+ if (totalTokens !== undefined) {
12551
+ usage.totalTokens = totalTokens;
12552
+ } else if (inputTokens !== undefined || outputTokens !== undefined) {
12553
+ usage.totalTokens = (inputTokens ?? 0) + (outputTokens ?? 0);
12554
+ }
12555
+ return usage;
12556
+ }
12557
+ function extractErrorFields(data) {
12558
+ const nested = asRecord5(data.error);
12559
+ const code = asString5(nested.code) ?? asString5(data.code);
12560
+ const message = asString5(nested.message) ?? asString5(data.message);
12561
+ const param = asString5(nested.param) ?? asString5(data.param);
12562
+ const out = {};
12563
+ if (code !== undefined) {
12564
+ out.code = code;
12565
+ }
12566
+ if (message !== undefined) {
12567
+ out.message = message;
12568
+ }
12569
+ if (param !== undefined) {
12570
+ out.param = param;
12571
+ }
12572
+ return out;
12573
+ }
12574
+ function classifyHttpError3(status, headers, code) {
12575
+ if (status === 401) {
12576
+ return { kind: "authentication", retryable: retryableFor4("authentication", false), message: "" };
12577
+ }
12578
+ if (status === 429) {
12579
+ const error = { kind: "rate_limit", retryable: retryableFor4("rate_limit", true), message: "" };
12580
+ const retryAfter = headers.get("retry-after");
12581
+ const seconds = retryAfter === null ? undefined : Number.parseInt(retryAfter, 10);
12582
+ if (seconds !== undefined && Number.isFinite(seconds)) {
12583
+ error.retryAfterMs = seconds * 1000;
12584
+ }
12585
+ return error;
12586
+ }
12587
+ if (status === 400) {
12588
+ if (code === "context_length_exceeded") {
12589
+ return { kind: "context_overflow", retryable: retryableFor4("context_overflow", false), message: "" };
12590
+ }
12591
+ return { kind: "invalid_request", retryable: retryableFor4("invalid_request", false), message: "" };
12592
+ }
12593
+ if (status >= 500) {
12594
+ return { kind: "unavailable", retryable: retryableFor4("unavailable", true), message: "" };
12595
+ }
12596
+ if (status >= 400) {
12597
+ return { kind: "invalid_request", retryable: retryableFor4("invalid_request", false), message: "" };
12598
+ }
12599
+ return { kind: "unknown", retryable: retryableFor4("unknown", false), message: "" };
12600
+ }
12601
+
12602
+ class OpenAiProvider {
12603
+ deps;
12604
+ constructor(deps) {
12605
+ this.deps = deps;
12606
+ }
12607
+ describe() {
12608
+ const capabilities = {
12609
+ streaming: true,
12610
+ toolCalls: true,
12611
+ parallelToolCalls: true,
12612
+ structuredOutput: true,
12613
+ reasoningMetadata: true,
12614
+ promptCaching: true,
12615
+ vision: false,
12616
+ tokenCounting: false,
12617
+ modelListing: false
12618
+ };
12619
+ return {
12620
+ capabilities,
12621
+ descriptor: { providerId: "openai", providerRevision: PROVIDER_REVISION4 }
12622
+ };
12623
+ }
12624
+ descriptorDocument() {
12625
+ return {
12626
+ schemaVersion: 1,
12627
+ providerId: "openai",
12628
+ providerRevision: PROVIDER_REVISION4,
12629
+ models: [{ modelId: DEFAULT_MODEL4.modelId, revision: DEFAULT_MODEL4.revision }],
12630
+ capabilities: {
12631
+ streaming: true,
12632
+ tools: true,
12633
+ parallelToolCalls: true,
12634
+ cancellation: true,
12635
+ structuredOutput: true
12636
+ },
12637
+ remoteState: { storage: false, retention: false, continuation: false }
12638
+ };
12639
+ }
12640
+ async* stream(request, opts) {
12641
+ let sequence = 0;
12642
+ const stamp = (body) => ({ ...body, sequence: sequence++, attemptId: opts.attemptId });
12643
+ const errorEvent = (error) => stamp({ kind: "provider_error", error });
12644
+ const grant = this.deps.grant;
12645
+ const redact = (message) => grant !== undefined && grant.apiKey.length > 0 ? message.split(grant.apiKey).join("[redacted]") : message;
12646
+ if (grant === undefined || grant.network !== true || typeof grant.apiKey !== "string" || grant.apiKey.length === 0) {
12647
+ yield errorEvent({
12648
+ kind: "authentication",
12649
+ retryable: retryableFor4("authentication", false),
12650
+ message: "network capability grant with an apiKey is required to reach the OpenAI API"
12651
+ });
12652
+ return;
11759
12653
  }
12654
+ const baseUrl = grant.baseUrl ?? DEFAULT_BASE_URL4;
12655
+ let host;
12656
+ try {
12657
+ host = new URL(baseUrl).hostname;
12658
+ } catch {
12659
+ host = baseUrl;
12660
+ }
12661
+ if (isPrivateEgressHost(host)) {
12662
+ yield errorEvent({
12663
+ kind: "invalid_request",
12664
+ retryable: retryableFor4("invalid_request", false),
12665
+ message: redact(`egress to a private/loopback/link-local/metadata host is denied: ${host}`)
12666
+ });
12667
+ return;
12668
+ }
12669
+ const url = `${baseUrl.replace(/\/+$/, "")}/v1/responses`;
12670
+ const headers = {
12671
+ authorization: `Bearer ${grant.apiKey}`,
12672
+ "content-type": "application/json"
12673
+ };
11760
12674
  const payload = {
11761
12675
  model: request.modelId,
12676
+ instructions: request.systemInstruction,
12677
+ input: toResponsesInput(request.messages),
11762
12678
  stream: true,
11763
- messages,
11764
12679
  ...request.tools !== undefined ? {
11765
12680
  tools: request.tools.map((tool) => ({
11766
12681
  type: "function",
11767
- function: {
11768
- name: tool.name,
11769
- ...tool.description !== undefined ? { description: tool.description } : {},
11770
- parameters: tool.inputSchema
11771
- }
11772
- }))
12682
+ name: tool.name,
12683
+ ...tool.description !== undefined ? { description: tool.description } : {},
12684
+ parameters: tool.inputSchema
12685
+ })),
12686
+ parallel_tool_calls: true
11773
12687
  } : {}
11774
12688
  };
11775
- const headers = { "content-type": "application/json" };
11776
- if (grant.apiKey !== undefined && grant.apiKey.length > 0) {
11777
- headers.authorization = `Bearer ${grant.apiKey}`;
11778
- }
11779
- if (grant.headers !== undefined) {
11780
- Object.assign(headers, grant.headers);
11781
- }
11782
12689
  const init = {
11783
12690
  method: "POST",
11784
12691
  headers,
@@ -11790,27 +12697,28 @@ ${message.content}` });
11790
12697
  response = await this.deps.fetch(url, init);
11791
12698
  } catch (cause) {
11792
12699
  if (opts.signal?.aborted === true) {
11793
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12700
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
11794
12701
  return;
11795
12702
  }
11796
12703
  yield errorEvent({
11797
12704
  kind: "unavailable",
11798
- retryable: retryableFor2("unavailable", true),
11799
- message: `network request to the Ollama API failed: ${String(cause)}`
12705
+ retryable: retryableFor4("unavailable", true),
12706
+ message: redact(`network request to the OpenAI API failed: ${String(cause)}`)
11800
12707
  });
11801
12708
  return;
11802
12709
  }
11803
12710
  if (!response.ok) {
11804
- const error = classifyHttpError2(response.status);
11805
- let providerMessage = `Ollama API returned HTTP ${response.status}`;
12711
+ let bodyRecord = {};
11806
12712
  try {
11807
- const parsed = asRecord3(JSON.parse(await response.text()));
11808
- const detail = asString3(asRecord3(parsed.error).message);
11809
- if (detail !== undefined && detail.length > 0) {
11810
- providerMessage = detail;
11811
- }
12713
+ bodyRecord = asRecord5(JSON.parse(await response.text()));
11812
12714
  } catch {}
11813
- error.message = providerMessage;
12715
+ const fields = extractErrorFields(bodyRecord);
12716
+ const error = classifyHttpError3(response.status, response.headers, fields.code);
12717
+ let providerMessage = `OpenAI API returned HTTP ${response.status}`;
12718
+ if (fields.message !== undefined && fields.message.length > 0) {
12719
+ providerMessage = fields.message;
12720
+ }
12721
+ error.message = redact(providerMessage);
11814
12722
  yield stamp({ kind: "provider_error", error });
11815
12723
  return;
11816
12724
  }
@@ -11820,21 +12728,21 @@ ${message.content}` });
11820
12728
  } catch (cause) {
11821
12729
  const aborted = opts.signal?.aborted === true || typeof cause === "object" && cause !== null && cause.name === "AbortError";
11822
12730
  if (aborted) {
11823
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12731
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
11824
12732
  return;
11825
12733
  }
11826
12734
  yield errorEvent({
11827
12735
  kind: "malformed",
11828
- retryable: retryableFor2("malformed", false),
11829
- message: `Ollama SSE body read failed: ${String(cause)}`
12736
+ retryable: retryableFor4("malformed", false),
12737
+ message: redact(`OpenAI SSE body read failed: ${String(cause)}`)
11830
12738
  });
11831
12739
  return;
11832
12740
  }
11833
12741
  if (bodyText.length === 0) {
11834
12742
  yield errorEvent({
11835
12743
  kind: "malformed",
11836
- retryable: retryableFor2("malformed", false),
11837
- message: "empty response body"
12744
+ retryable: retryableFor4("malformed", false),
12745
+ message: redact("empty response body")
11838
12746
  });
11839
12747
  return;
11840
12748
  }
@@ -11842,149 +12750,193 @@ ${message.content}` });
11842
12750
  const records = parser.push(bodyText);
11843
12751
  const torn = parser.flush();
11844
12752
  const bodies = [];
12753
+ const pendingTools = new Map;
11845
12754
  let sawStart = false;
11846
- let sawFinish = false;
11847
- let sawDone = false;
12755
+ let sawCompleted = false;
11848
12756
  let malformed;
11849
- const pendingTools = new Map;
11850
- const toolCallKey = (toolCall) => {
11851
- const index = asNumber3(toolCall.index);
11852
- if (index !== undefined) {
11853
- return `idx:${index}`;
11854
- }
11855
- const id = asString3(toolCall.id);
11856
- if (id !== undefined && id.length > 0) {
11857
- return `id:${id}`;
11858
- }
11859
- return "idx:0";
11860
- };
11861
- const flushPendingToolEnds = () => {
11862
- for (const acc of pendingTools.values()) {
11863
- if (acc.ended) {
11864
- continue;
11865
- }
11866
- if (!acc.started) {
11867
- const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11868
- if (acc.name.length > 0) {
11869
- startBody.toolName = acc.name;
11870
- }
11871
- bodies.push(startBody);
11872
- acc.started = true;
11873
- }
11874
- bodies.push({ kind: "tool_call_end", toolCallId: acc.id, input: acc.arguments });
11875
- acc.ended = true;
11876
- }
11877
- pendingTools.clear();
11878
- };
12757
+ let terminalError;
11879
12758
  for (const record of records) {
11880
- const trimmed = record.data.trim();
11881
- if (trimmed === "[DONE]") {
11882
- sawDone = true;
11883
- flushPendingToolEnds();
11884
- continue;
11885
- }
11886
- if (!sawStart) {
11887
- sawStart = true;
11888
- bodies.push({ kind: "model_start" });
11889
- }
11890
12759
  let parsed;
11891
12760
  try {
11892
12761
  parsed = JSON.parse(record.data);
11893
12762
  } catch {
11894
12763
  malformed = {
11895
12764
  kind: "malformed",
11896
- retryable: retryableFor2("malformed", false),
11897
- message: "Ollama SSE data line was not valid JSON"
12765
+ retryable: retryableFor4("malformed", false),
12766
+ message: redact("OpenAI SSE data line was not valid JSON")
11898
12767
  };
11899
12768
  break;
11900
12769
  }
11901
- const data = asRecord3(parsed);
11902
- if (data.usage !== undefined) {
11903
- const usage = asRecord3(data.usage);
11904
- bodies.push({
11905
- kind: "usage_update",
11906
- usage: mergeUsage2(asNumber3(usage.prompt_tokens), asNumber3(usage.completion_tokens), asNumber3(usage.total_tokens))
11907
- });
11908
- }
11909
- const choice0 = asRecord3(asArray(data.choices)[0]);
11910
- const delta = asRecord3(choice0.delta);
11911
- const reasoning = asString3(delta.reasoning) ?? asString3(delta.reasoning_content);
11912
- if (reasoning !== undefined && reasoning.length > 0) {
11913
- bodies.push({ kind: "reasoning_delta", text: reasoning });
11914
- }
11915
- const content = asString3(delta.content);
11916
- if (content !== undefined && content.length > 0) {
11917
- bodies.push({ kind: "text_delta", text: content });
11918
- }
11919
- for (const rawToolCall of asArray(delta.tool_calls)) {
11920
- const toolCall = asRecord3(rawToolCall);
11921
- const fn = asRecord3(toolCall.function);
11922
- const toolCallId = asString3(toolCall.id);
11923
- const toolName = asString3(fn.name);
11924
- const argumentsFragment = asString3(fn.arguments) ?? "";
11925
- const key = toolCallKey(toolCall);
11926
- let acc = pendingTools.get(key);
11927
- if (acc === undefined) {
11928
- acc = {
11929
- id: toolCallId ?? `call_${key.replace(/[^a-zA-Z0-9_:-]/g, "_")}`,
11930
- name: toolName ?? "",
11931
- arguments: "",
11932
- started: false,
11933
- ended: false
11934
- };
11935
- pendingTools.set(key, acc);
12770
+ const data = asRecord5(parsed);
12771
+ const eventType = asString5(data.type) ?? asString5(record.event);
12772
+ switch (eventType) {
12773
+ case "response.created": {
12774
+ break;
11936
12775
  }
11937
- if (toolCallId !== undefined && toolCallId.length > 0) {
11938
- acc.id = toolCallId;
12776
+ case "response.output_item.added": {
12777
+ const item = asRecord5(data.item);
12778
+ if (asString5(item.type) === "function_call") {
12779
+ if (!sawStart) {
12780
+ sawStart = true;
12781
+ bodies.push({ kind: "model_start" });
12782
+ }
12783
+ const itemId = asString5(data.item_id) ?? asString5(item.id) ?? "";
12784
+ const callId = asString5(item.call_id) ?? itemId;
12785
+ const toolName = asString5(item.name);
12786
+ pendingTools.set(itemId, { callId, name: toolName ?? "", arguments: asString5(item.arguments) ?? "" });
12787
+ const startBody = { kind: "tool_call_start", toolCallId: callId };
12788
+ if (toolName !== undefined) {
12789
+ startBody.toolName = toolName;
12790
+ }
12791
+ bodies.push(startBody);
12792
+ }
12793
+ break;
11939
12794
  }
11940
- if (toolName !== undefined && toolName.length > 0) {
11941
- acc.name = toolName;
12795
+ case "response.output_text.delta": {
12796
+ if (!sawStart) {
12797
+ sawStart = true;
12798
+ bodies.push({ kind: "model_start" });
12799
+ }
12800
+ const text = asString5(data.delta);
12801
+ const body = { kind: "text_delta" };
12802
+ if (text !== undefined) {
12803
+ body.text = text;
12804
+ }
12805
+ bodies.push(body);
12806
+ break;
11942
12807
  }
11943
- if (!acc.started) {
11944
- const startBody = { kind: "tool_call_start", toolCallId: acc.id };
11945
- if (acc.name.length > 0) {
11946
- startBody.toolName = acc.name;
12808
+ case "response.reasoning_summary_text.delta": {
12809
+ if (!sawStart) {
12810
+ sawStart = true;
12811
+ bodies.push({ kind: "model_start" });
11947
12812
  }
11948
- bodies.push(startBody);
11949
- acc.started = true;
12813
+ const text = asString5(data.delta);
12814
+ if (text !== undefined && text.length > 0) {
12815
+ bodies.push({ kind: "reasoning_delta", text });
12816
+ }
12817
+ break;
11950
12818
  }
11951
- if (argumentsFragment.length > 0) {
11952
- acc.arguments += argumentsFragment;
11953
- bodies.push({
11954
- kind: "tool_call_delta",
11955
- toolCallId: acc.id,
11956
- inputDelta: argumentsFragment
11957
- });
12819
+ case "response.function_call_arguments.delta": {
12820
+ const itemId = asString5(data.item_id) ?? "";
12821
+ const fragment = asString5(data.delta) ?? "";
12822
+ const pending = pendingTools.get(itemId);
12823
+ const toolCallId = pending?.callId ?? itemId;
12824
+ if (pending !== undefined) {
12825
+ pending.arguments += fragment;
12826
+ }
12827
+ if (fragment.length > 0) {
12828
+ bodies.push({ kind: "tool_call_delta", toolCallId, inputDelta: fragment });
12829
+ }
12830
+ break;
12831
+ }
12832
+ case "response.function_call_arguments.done": {
12833
+ const itemId = asString5(data.item_id) ?? "";
12834
+ const pending = pendingTools.get(itemId);
12835
+ const fullArguments = asString5(data.arguments) ?? pending?.arguments ?? "";
12836
+ const toolCallId = pending?.callId ?? itemId;
12837
+ bodies.push({ kind: "tool_call_end", toolCallId, input: fullArguments });
12838
+ pendingTools.delete(itemId);
12839
+ break;
12840
+ }
12841
+ case "response.output_item.done": {
12842
+ const item = asRecord5(data.item);
12843
+ if (asString5(item.type) === "function_call") {
12844
+ const itemId = asString5(data.item_id) ?? asString5(item.id) ?? "";
12845
+ const pending = pendingTools.get(itemId);
12846
+ if (pending !== undefined) {
12847
+ const fullArguments = asString5(item.arguments) ?? pending.arguments;
12848
+ bodies.push({ kind: "tool_call_end", toolCallId: pending.callId, input: fullArguments });
12849
+ pendingTools.delete(itemId);
12850
+ }
12851
+ }
12852
+ break;
12853
+ }
12854
+ case "response.completed": {
12855
+ sawCompleted = true;
12856
+ const usage = asRecord5(asRecord5(data.response).usage);
12857
+ const inputTokens = asNumber5(usage.input_tokens);
12858
+ const outputTokens = asNumber5(usage.output_tokens);
12859
+ const totalTokens = asNumber5(usage.total_tokens);
12860
+ const reasoningTokens = asNumber5(asRecord5(usage.output_tokens_details).reasoning_tokens);
12861
+ const usageBody = {
12862
+ kind: "usage_update",
12863
+ usage: mergeUsage4(inputTokens, outputTokens, totalTokens)
12864
+ };
12865
+ if (reasoningTokens !== undefined) {
12866
+ usageBody.unknownExtensions = { "openai.reasoning_tokens": reasoningTokens };
12867
+ }
12868
+ bodies.push(usageBody);
12869
+ bodies.push({ kind: "model_end" });
12870
+ break;
12871
+ }
12872
+ case "response.failed":
12873
+ case "response.incomplete": {
12874
+ const fields = extractErrorFields(asRecord5(data.response));
12875
+ const code = fields.code;
12876
+ let kind = "unknown";
12877
+ if (code === "context_length_exceeded") {
12878
+ kind = "context_overflow";
12879
+ } else if (code !== undefined && code.length > 0) {
12880
+ kind = "invalid_request";
12881
+ }
12882
+ let message = fields.message;
12883
+ if (message === undefined || message.length === 0) {
12884
+ message = kind === "context_overflow" ? "OpenAI Responses API returned an error with no message; likely context-length overflow" : "OpenAI Responses API returned an error with no message";
12885
+ }
12886
+ terminalError = { kind, retryable: retryableFor4(kind, false), message: redact(message) };
12887
+ break;
11958
12888
  }
12889
+ case "error": {
12890
+ const fields = extractErrorFields(data);
12891
+ const code = fields.code;
12892
+ let kind = "unknown";
12893
+ if (code === "context_length_exceeded") {
12894
+ kind = "context_overflow";
12895
+ } else if (code !== undefined && code.length > 0) {
12896
+ kind = "invalid_request";
12897
+ }
12898
+ let message = fields.message;
12899
+ if (message === undefined || message.length === 0) {
12900
+ message = kind === "context_overflow" ? "OpenAI Responses API returned an error with no message; likely context-length overflow" : "OpenAI Responses API returned an error with no message; likely context-length overflow (unconfirmed cause)";
12901
+ }
12902
+ terminalError = { kind, retryable: retryableFor4(kind, false), message: redact(message) };
12903
+ break;
12904
+ }
12905
+ default:
12906
+ break;
11959
12907
  }
11960
- const finishReason = asString3(choice0.finish_reason);
11961
- if (finishReason !== undefined && finishReason.length > 0) {
11962
- sawFinish = true;
11963
- flushPendingToolEnds();
12908
+ if (terminalError !== undefined) {
12909
+ break;
11964
12910
  }
11965
12911
  }
11966
- if (malformed === undefined && torn.length > 0) {
11967
- malformed = {
11968
- kind: "malformed",
11969
- retryable: retryableFor2("malformed", false),
11970
- message: "Ollama SSE stream ended mid-record (torn stream)"
11971
- };
11972
- }
11973
- if (malformed === undefined) {
11974
- flushPendingToolEnds();
11975
- }
11976
- if (malformed === undefined && sawStart && (sawDone || sawFinish)) {
11977
- bodies.push({ kind: "model_end" });
12912
+ if (malformed === undefined && terminalError === undefined) {
12913
+ if (torn.length > 0) {
12914
+ malformed = {
12915
+ kind: "malformed",
12916
+ retryable: retryableFor4("malformed", false),
12917
+ message: redact("OpenAI SSE stream ended mid-record (torn stream)")
12918
+ };
12919
+ } else if (sawStart && !sawCompleted) {
12920
+ malformed = {
12921
+ kind: "malformed",
12922
+ retryable: retryableFor4("malformed", false),
12923
+ message: redact("OpenAI SSE stream ended before response.completed (truncated stream)")
12924
+ };
12925
+ }
11978
12926
  }
11979
12927
  for (const body of bodies) {
11980
12928
  if (opts.signal?.aborted === true) {
11981
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12929
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
11982
12930
  return;
11983
12931
  }
11984
12932
  yield stamp(body);
11985
12933
  }
11986
12934
  if (opts.signal?.aborted === true) {
11987
- yield errorEvent({ kind: "cancelled", retryable: retryableFor2("cancelled", false), message: "attempt cancelled" });
12935
+ yield errorEvent({ kind: "cancelled", retryable: retryableFor4("cancelled", false), message: "attempt cancelled" });
12936
+ return;
12937
+ }
12938
+ if (terminalError !== undefined) {
12939
+ yield stamp({ kind: "provider_error", error: terminalError });
11988
12940
  return;
11989
12941
  }
11990
12942
  if (malformed !== undefined) {
@@ -11992,13 +12944,13 @@ ${message.content}` });
11992
12944
  }
11993
12945
  }
11994
12946
  }
11995
- var DEFAULT_BASE_URL2 = "http://localhost:11434", PROVIDER_REVISION2 = "ollama-2024-10-22", DEFAULT_MODEL2;
11996
- var init_ollama_provider = __esm(() => {
12947
+ var DEFAULT_BASE_URL4 = "https://api.openai.com", PROVIDER_REVISION4 = "openai-responses-2026-08", DEFAULT_MODEL4;
12948
+ var init_openai_provider = __esm(() => {
11997
12949
  init_guard2();
11998
12950
  init_provider_port();
11999
- DEFAULT_MODEL2 = {
12000
- modelId: "llama3.1:latest",
12001
- revision: "latest"
12951
+ DEFAULT_MODEL4 = {
12952
+ modelId: "gpt-4.1",
12953
+ revision: "2026-08"
12002
12954
  };
12003
12955
  });
12004
12956
 
@@ -12012,6 +12964,20 @@ function makeProvider(name, _model, opts) {
12012
12964
  }
12013
12965
  return new AnthropicProvider({ fetch: opts.fetch, grant: { network: true, apiKey } });
12014
12966
  }
12967
+ if (name === "openai") {
12968
+ const apiKey = env.OPENAI_API_KEY;
12969
+ if (apiKey === undefined || apiKey.length === 0) {
12970
+ return new FakeProvider([]);
12971
+ }
12972
+ return new OpenAiProvider({ fetch: opts.fetch, grant: { network: true, apiKey } });
12973
+ }
12974
+ if (name === "gemini") {
12975
+ const apiKey = env.GEMINI_API_KEY !== undefined && env.GEMINI_API_KEY.length > 0 ? env.GEMINI_API_KEY : env.GOOGLE_API_KEY;
12976
+ if (apiKey === undefined || apiKey.length === 0) {
12977
+ return new FakeProvider([]);
12978
+ }
12979
+ return new GeminiProvider({ fetch: opts.fetch, grant: { network: true, apiKey } });
12980
+ }
12015
12981
  if (name === "ollama") {
12016
12982
  return new OllamaProvider({
12017
12983
  fetch: opts.fetch,
@@ -12032,18 +12998,26 @@ function makeProvider(name, _model, opts) {
12032
12998
  ...compat.chatPath !== undefined ? { chatPath: compat.chatPath } : {},
12033
12999
  ...apiKey !== undefined ? { apiKey } : {}
12034
13000
  };
12035
- return new OllamaProvider({
12036
- fetch: opts.fetch,
12037
- grant
12038
- });
13001
+ return new OpenAiCompatEngine({ fetch: opts.fetch, grant }, OLLAMA_COMPAT_IDENTITY);
12039
13002
  }
12040
13003
  return new FakeProvider([]);
12041
13004
  }
13005
+ var OLLAMA_COMPAT_IDENTITY;
12042
13006
  var init_make_provider = __esm(() => {
12043
13007
  init_providers();
12044
13008
  init_anthropic_provider();
13009
+ init_openai_compat_provider();
12045
13010
  init_fake_provider();
13011
+ init_gemini_provider();
12046
13012
  init_ollama_provider();
13013
+ init_openai_provider();
13014
+ OLLAMA_COMPAT_IDENTITY = {
13015
+ defaultBaseUrl: "http://localhost:11434",
13016
+ providerRevision: "ollama-2024-10-22",
13017
+ providerId: "ollama",
13018
+ providerLabel: "Ollama",
13019
+ defaultModel: { modelId: "llama3.1:latest", revision: "latest" }
13020
+ };
12047
13021
  });
12048
13022
 
12049
13023
  // src/harness/provider/single-turn.ts
@@ -12062,6 +13036,14 @@ function hasCredential(provider, env) {
12062
13036
  const key = env.ANTHROPIC_API_KEY;
12063
13037
  return key !== undefined && key.length > 0;
12064
13038
  }
13039
+ if (provider === "openai") {
13040
+ const key = env.OPENAI_API_KEY;
13041
+ return key !== undefined && key.length > 0;
13042
+ }
13043
+ if (provider === "gemini") {
13044
+ const key = env.GEMINI_API_KEY ?? env.GOOGLE_API_KEY;
13045
+ return key !== undefined && key.length > 0;
13046
+ }
12065
13047
  const compat = providerByName(provider);
12066
13048
  if (compat) {
12067
13049
  if (compat.requiresApiKey === false) {
@@ -12078,6 +13060,8 @@ function hasCredential(provider, env) {
12078
13060
  function keyedProviderCandidates() {
12079
13061
  return [
12080
13062
  "anthropic",
13063
+ "openai",
13064
+ "gemini",
12081
13065
  ...OPENAI_COMPAT_PROVIDERS.filter((provider) => provider.requiresApiKey !== false).map((p) => p.name)
12082
13066
  ];
12083
13067
  }
@@ -12153,7 +13137,9 @@ var init_single_turn = __esm(() => {
12153
13137
  init_shell_config();
12154
13138
  DEFAULT_MODELS = {
12155
13139
  anthropic: "claude-haiku-4-5-20251001",
12156
- ollama: "llama3.2"
13140
+ ollama: "llama3.2",
13141
+ openai: "gpt-5.6-luna",
13142
+ gemini: "gemini-2.5-flash-lite"
12157
13143
  };
12158
13144
  });
12159
13145
 
@@ -13196,23 +14182,23 @@ var init_slate = __esm(() => {
13196
14182
  // src/flow/store.ts
13197
14183
  var exports_store = {};
13198
14184
  __export(exports_store, {
13199
- writeFlow: () => writeFlow,
13200
- slugify: () => slugify2,
13201
- resolveFlowDir: () => resolveFlowDir,
13202
- readFlow: () => readFlow,
13203
- readAcCriteria: () => readAcCriteria,
13204
- nextFlowId: () => nextFlowId,
13205
- migrateFlow: () => migrateFlow,
13206
- listFlowDirs: () => listFlowDirs,
13207
- groupFlowDirsById: () => groupFlowDirsById,
13208
- flowsRoot: () => flowsRoot,
13209
- flowIdOf: () => flowIdOf,
13210
- duplicateFlowIds: () => duplicateFlowIds,
13211
- deriveFlowWork: () => deriveFlowWork,
13212
- assertAcIntact: () => assertAcIntact,
13213
- appendJournal: () => appendJournal,
14185
+ acChecksum: () => acChecksum,
13214
14186
  acPath: () => acPath,
13215
- acChecksum: () => acChecksum
14187
+ appendJournal: () => appendJournal,
14188
+ assertAcIntact: () => assertAcIntact,
14189
+ deriveFlowWork: () => deriveFlowWork,
14190
+ duplicateFlowIds: () => duplicateFlowIds,
14191
+ flowIdOf: () => flowIdOf,
14192
+ flowsRoot: () => flowsRoot,
14193
+ groupFlowDirsById: () => groupFlowDirsById,
14194
+ listFlowDirs: () => listFlowDirs,
14195
+ migrateFlow: () => migrateFlow,
14196
+ nextFlowId: () => nextFlowId,
14197
+ readAcCriteria: () => readAcCriteria,
14198
+ readFlow: () => readFlow,
14199
+ resolveFlowDir: () => resolveFlowDir,
14200
+ slugify: () => slugify2,
14201
+ writeFlow: () => writeFlow
13216
14202
  });
13217
14203
  import { createHash as createHash7 } from "crypto";
13218
14204
  import { appendFile as appendFile2, mkdir as mkdir26, readFile as readFile32, readdir as readdir8 } from "fs/promises";
@@ -17897,7 +18883,7 @@ function buildAgentSystemInstruction(orient, ctx = {}) {
17897
18883
  Project orientation (trusted context):
17898
18884
  ${trimmed}`;
17899
18885
  }
17900
- function parseToolInput2(raw) {
18886
+ function parseToolInput3(raw) {
17901
18887
  const text = raw.trim();
17902
18888
  if (text.length === 0) {
17903
18889
  return {};
@@ -17940,7 +18926,7 @@ function stableStringify2(value) {
17940
18926
  return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify2(obj[k])}`).join(",")}}`;
17941
18927
  }
17942
18928
  function toolCallHash(name, input2) {
17943
- const parsed = parseToolInput2(input2);
18929
+ const parsed = parseToolInput3(input2);
17944
18930
  return `${name}\x00${stableStringify2(parsed)}`;
17945
18931
  }
17946
18932
  function budgetUsed(state) {
@@ -18371,7 +19357,7 @@ ${modelOutput}` : modelOutput,
18371
19357
  }
18372
19358
  if (options.slateSession !== undefined && options.slateSession.opened === true) {
18373
19359
  try {
18374
- const touchedPaths = extractTouchedFromToolInput(call.name, parseToolInput2(call.input));
19360
+ const touchedPaths = extractTouchedFromToolInput(call.name, parseToolInput3(call.input));
18375
19361
  const touch = await recordSlateTouch(options.slateSession.dir, touchedPaths, {
18376
19362
  runtime: { provider: deps.providerId, model: deps.modelId }
18377
19363
  });
@@ -18630,7 +19616,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
18630
19616
  if (tool === undefined) {
18631
19617
  return { output: `unknown tool: ${call.name}`, isError: true };
18632
19618
  }
18633
- const input2 = parseToolInput2(call.input);
19619
+ const input2 = parseToolInput3(call.input);
18634
19620
  const validation = validateAgainstSchemaObject(tool.definition.inputSchema, input2);
18635
19621
  if (!validation.valid) {
18636
19622
  const detail = validation.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
@@ -23336,7 +24322,7 @@ var init_github = __esm(() => {
23336
24322
  });
23337
24323
 
23338
24324
  // src/harness/tool/metaproject-adapter.ts
23339
- import { readFile as readFile55 } from "fs/promises";
24325
+ import { readdir as readdir14, readFile as readFile55 } from "fs/promises";
23340
24326
  import { isAbsolute, join, relative, resolve } from "path";
23341
24327
  function errorMessage(cause) {
23342
24328
  return cause instanceof Error ? cause.message : String(cause);
@@ -23357,6 +24343,131 @@ function confineToWiki(cwd, candidate) {
23357
24343
  }
23358
24344
  return target;
23359
24345
  }
24346
+ function confineToSkills(cwd, candidate) {
24347
+ const skillsRoot = join(cwd, ".metaproject", "skills", "gdskills");
24348
+ const target = resolve(cwd, candidate);
24349
+ const rel = relative(skillsRoot, target);
24350
+ if (rel === "") {
24351
+ return null;
24352
+ }
24353
+ if (rel.startsWith("..") || isAbsolute(rel)) {
24354
+ return null;
24355
+ }
24356
+ return target;
24357
+ }
24358
+ function stripSkillFieldQuotes(value) {
24359
+ if (value.length >= 2) {
24360
+ const first = value[0];
24361
+ const last = value[value.length - 1];
24362
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
24363
+ return value.slice(1, -1);
24364
+ }
24365
+ }
24366
+ return value;
24367
+ }
24368
+ function parseSkillFrontmatter(content) {
24369
+ if (!content.startsWith("---")) {
24370
+ return {};
24371
+ }
24372
+ const end = content.indexOf(`
24373
+ ---`, 3);
24374
+ if (end === -1) {
24375
+ return {};
24376
+ }
24377
+ const lines = content.slice(3, end).split(`
24378
+ `);
24379
+ let description;
24380
+ const triggers = [];
24381
+ let inTriggers = false;
24382
+ for (const line of lines) {
24383
+ const descMatch = /^description:\s*(.*)$/.exec(line);
24384
+ if (descMatch !== null && descMatch[1] !== undefined) {
24385
+ description = stripSkillFieldQuotes(descMatch[1].trim());
24386
+ inTriggers = false;
24387
+ continue;
24388
+ }
24389
+ if (/^triggers:\s*$/.test(line)) {
24390
+ inTriggers = true;
24391
+ continue;
24392
+ }
24393
+ if (inTriggers) {
24394
+ const itemMatch = /^\s+-\s*(.+)$/.exec(line);
24395
+ if (itemMatch !== null && itemMatch[1] !== undefined) {
24396
+ triggers.push(stripSkillFieldQuotes(itemMatch[1].trim()));
24397
+ continue;
24398
+ }
24399
+ inTriggers = false;
24400
+ }
24401
+ }
24402
+ return { ...description !== undefined ? { description } : {}, ...triggers.length > 0 ? { triggers } : {} };
24403
+ }
24404
+ async function parseCatalogSummaries(cwd) {
24405
+ const summaries = new Map;
24406
+ let content;
24407
+ try {
24408
+ content = await readFile55(join(cwd, ".metaproject", "skills", "catalog.md"), "utf8");
24409
+ } catch {
24410
+ return summaries;
24411
+ }
24412
+ const rowPattern = /^\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*([^|]+?)\s*\|\s*$/;
24413
+ for (const line of content.split(`
24414
+ `)) {
24415
+ const match = rowPattern.exec(line);
24416
+ if (match === null) {
24417
+ continue;
24418
+ }
24419
+ const [, name, , purpose] = match;
24420
+ if (name === undefined || purpose === undefined || name === "Skill" || /^-+$/.test(name)) {
24421
+ continue;
24422
+ }
24423
+ summaries.set(name, purpose);
24424
+ }
24425
+ return summaries;
24426
+ }
24427
+ async function walkSkillCatalog(cwd) {
24428
+ const root = join(cwd, ".metaproject", "skills", "gdskills");
24429
+ const entries = [];
24430
+ let categoryDirs;
24431
+ try {
24432
+ categoryDirs = await readdir14(root, { withFileTypes: true });
24433
+ } catch {
24434
+ return [];
24435
+ }
24436
+ const catalogSummaries = await parseCatalogSummaries(cwd);
24437
+ for (const categoryDir of categoryDirs) {
24438
+ if (!categoryDir.isDirectory()) {
24439
+ continue;
24440
+ }
24441
+ const categoryPath = join(root, categoryDir.name);
24442
+ let skillDirs;
24443
+ try {
24444
+ skillDirs = await readdir14(categoryPath, { withFileTypes: true });
24445
+ } catch {
24446
+ continue;
24447
+ }
24448
+ for (const skillDir of skillDirs) {
24449
+ if (!skillDir.isDirectory()) {
24450
+ continue;
24451
+ }
24452
+ const skillMdPath = join(categoryPath, skillDir.name, "SKILL.md");
24453
+ let content;
24454
+ try {
24455
+ content = await readFile55(skillMdPath, "utf8");
24456
+ } catch {
24457
+ continue;
24458
+ }
24459
+ const { description, triggers } = parseSkillFrontmatter(content);
24460
+ entries.push({
24461
+ name: skillDir.name,
24462
+ path: relative(cwd, skillMdPath),
24463
+ category: categoryDir.name,
24464
+ description: description ?? catalogSummaries.get(skillDir.name) ?? "",
24465
+ ...triggers !== undefined ? { triggers } : {}
24466
+ });
24467
+ }
24468
+ }
24469
+ return entries.sort((a, b) => a.path.localeCompare(b.path));
24470
+ }
23360
24471
  function createMetaprojectAdapter(cwd, overrides = {}) {
23361
24472
  const deps = { ...DEFAULT_DEPS, ...overrides };
23362
24473
  const gdgraph = deps.createGdgraphService();
@@ -23624,6 +24735,36 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
23624
24735
  } catch (cause) {
23625
24736
  return { file: input2.file, backlinks: [], error: errorMessage(cause) };
23626
24737
  }
24738
+ },
24739
+ async skillsCatalog() {
24740
+ const skills = await walkSkillCatalog(cwd);
24741
+ return { skills, generatedAt: deps.now() };
24742
+ },
24743
+ async loadSkill(input2) {
24744
+ const catalog = await walkSkillCatalog(cwd);
24745
+ const byName = catalog.find((entry) => entry.name === input2.name);
24746
+ if (byName !== undefined) {
24747
+ try {
24748
+ const content = await readFile55(join(cwd, byName.path), "utf8");
24749
+ return { name: input2.name, path: byName.path, content, found: true };
24750
+ } catch {
24751
+ return { name: input2.name, path: "", content: "", found: false };
24752
+ }
24753
+ }
24754
+ const confined = confineToSkills(cwd, input2.name);
24755
+ if (confined === null) {
24756
+ return { name: input2.name, path: "", content: "", found: false };
24757
+ }
24758
+ const byPath = catalog.find((entry) => join(cwd, entry.path) === confined);
24759
+ if (byPath === undefined) {
24760
+ return { name: input2.name, path: "", content: "", found: false };
24761
+ }
24762
+ try {
24763
+ const content = await readFile55(confined, "utf8");
24764
+ return { name: input2.name, path: byPath.path, content, found: true };
24765
+ } catch {
24766
+ return { name: input2.name, path: "", content: "", found: false };
24767
+ }
23627
24768
  }
23628
24769
  };
23629
24770
  }
@@ -23660,6 +24801,7 @@ var init_metaproject_adapter = __esm(() => {
23660
24801
  }),
23661
24802
  wikiAsk,
23662
24803
  wikiPagesForFile,
24804
+ now: () => new Date().toISOString(),
23663
24805
  repomapCompute: async (cwd, options) => {
23664
24806
  const [graph, config] = await Promise.all([loadGraph(cwd), loadGdgraphConfig(cwd)]);
23665
24807
  return computeRepomap(graph, config, options);
@@ -23734,6 +24876,24 @@ function formatWiki(result) {
23734
24876
  }
23735
24877
  return { output: result.content.length > 0 ? result.content : "(empty page)", isError: false };
23736
24878
  }
24879
+ function formatSkillsCatalog(result) {
24880
+ if (result.skills.length === 0) {
24881
+ return { output: "No skills found under .metaproject/skills/gdskills/.", isError: false };
24882
+ }
24883
+ const lines = result.skills.map((skill2) => {
24884
+ const triggers = skill2.triggers !== undefined && skill2.triggers.length > 0 ? ` [triggers: ${skill2.triggers.join(", ")}]` : "";
24885
+ return ` - ${skill2.name} (${skill2.category}) \u2014 ${skill2.description || "(no description)"}${triggers}
24886
+ ${skill2.path}`;
24887
+ });
24888
+ return { output: [`Skills (${result.skills.length}):`, ...lines].join(`
24889
+ `), isError: false };
24890
+ }
24891
+ function formatSkillLoad(result) {
24892
+ if (!result.found) {
24893
+ return { output: `skill_load: no skill found for '${result.name}'.`, isError: true };
24894
+ }
24895
+ return { output: result.content, isError: false };
24896
+ }
23737
24897
  function formatPath(result) {
23738
24898
  if (result.error !== undefined) {
23739
24899
  return { output: `graph_path failed: ${result.error}`, isError: true };
@@ -23847,7 +25007,7 @@ function toInteractiveTools(ops, port) {
23847
25007
  invoke: (input2) => op.invoke(port, input2)
23848
25008
  }));
23849
25009
  }
23850
- var AFFECTED_OUTPUT_SCHEMA, QUERY_OUTPUT_SCHEMA, MEMORY_OUTPUT_SCHEMA, WIKI_OUTPUT_SCHEMA, SEARCH_OUTPUT_SCHEMA, PATH_OUTPUT_SCHEMA, TEST_RELATED_OUTPUT_SCHEMA, HEALTH_OUTPUT_SCHEMA, SYMBOL_OUTPUT_SCHEMA, REPOMAP_OUTPUT_SCHEMA, WIKI_ASK_OUTPUT_SCHEMA, WIKI_BACKLINKS_OUTPUT_SCHEMA, FLOW_STATUS_OUTPUT_SCHEMA, METAPROJECT_OPERATIONS;
25010
+ var AFFECTED_OUTPUT_SCHEMA, QUERY_OUTPUT_SCHEMA, MEMORY_OUTPUT_SCHEMA, WIKI_OUTPUT_SCHEMA, SEARCH_OUTPUT_SCHEMA, PATH_OUTPUT_SCHEMA, TEST_RELATED_OUTPUT_SCHEMA, HEALTH_OUTPUT_SCHEMA, SYMBOL_OUTPUT_SCHEMA, REPOMAP_OUTPUT_SCHEMA, WIKI_ASK_OUTPUT_SCHEMA, WIKI_BACKLINKS_OUTPUT_SCHEMA, FLOW_STATUS_OUTPUT_SCHEMA, SKILLS_CATALOG_OUTPUT_SCHEMA, SKILL_LOAD_OUTPUT_SCHEMA, METAPROJECT_OPERATIONS;
23851
25011
  var init_metaproject_operations = __esm(() => {
23852
25012
  AFFECTED_OUTPUT_SCHEMA = {
23853
25013
  type: "object",
@@ -23984,6 +25144,24 @@ var init_metaproject_operations = __esm(() => {
23984
25144
  },
23985
25145
  required: ["flows"]
23986
25146
  };
25147
+ SKILLS_CATALOG_OUTPUT_SCHEMA = {
25148
+ type: "object",
25149
+ properties: {
25150
+ skills: { type: "array" },
25151
+ generatedAt: { type: "string" }
25152
+ },
25153
+ required: ["skills", "generatedAt"]
25154
+ };
25155
+ SKILL_LOAD_OUTPUT_SCHEMA = {
25156
+ type: "object",
25157
+ properties: {
25158
+ name: { type: "string" },
25159
+ path: { type: "string" },
25160
+ content: { type: "string" },
25161
+ found: { type: "boolean" }
25162
+ },
25163
+ required: ["name", "path", "content", "found"]
25164
+ };
23987
25165
  METAPROJECT_OPERATIONS = [
23988
25166
  {
23989
25167
  name: "search_code",
@@ -24260,6 +25438,47 @@ var init_metaproject_operations = __esm(() => {
24260
25438
  const id = typeof input2.id === "string" && input2.id.trim().length > 0 ? input2.id.trim() : undefined;
24261
25439
  return formatFlowStatus(await port.flowStatus(id !== undefined ? { id } : {}));
24262
25440
  }
25441
+ },
25442
+ {
25443
+ name: "skills_catalog",
25444
+ risk: "read",
25445
+ module: "gdskills",
25446
+ description: "List every skill discovered under .metaproject/skills/gdskills/ \u2014 name, category, description, and " + "triggers for each. Use this instead of reading .metaproject/index.md or catalog.md to find which skill " + "applies to a task. No input.",
25447
+ inputSchema: {
25448
+ type: "object",
25449
+ properties: {},
25450
+ additionalProperties: false
25451
+ },
25452
+ outputSchema: SKILLS_CATALOG_OUTPUT_SCHEMA,
25453
+ invoke: async (port) => {
25454
+ if (port.skillsCatalog === undefined) {
25455
+ return { output: "skills_catalog is not available in this session.", isError: true };
25456
+ }
25457
+ return formatSkillsCatalog(await port.skillsCatalog({}));
25458
+ }
25459
+ },
25460
+ {
25461
+ name: "skill_load",
25462
+ risk: "read",
25463
+ module: "gdskills",
25464
+ description: `Load one skill's full SKILL.md content by name (e.g. "flow-orchestrator") or exact project-relative ` + "path, as returned by `skills_catalog`. Input: { name: string }.",
25465
+ inputSchema: {
25466
+ type: "object",
25467
+ properties: { name: { type: "string" } },
25468
+ required: ["name"],
25469
+ additionalProperties: false
25470
+ },
25471
+ outputSchema: SKILL_LOAD_OUTPUT_SCHEMA,
25472
+ invoke: async (port, input2) => {
25473
+ if (port.loadSkill === undefined) {
25474
+ return { output: "skill_load is not available in this session.", isError: true };
25475
+ }
25476
+ const name = requireString(input2, "name", "skill_load");
25477
+ if ("error" in name) {
25478
+ return name.error;
25479
+ }
25480
+ return formatSkillLoad(await port.loadSkill({ name: name.value }));
25481
+ }
24263
25482
  }
24264
25483
  ];
24265
25484
  });
@@ -24513,30 +25732,30 @@ var init_staleness2 = __esm(() => {
24513
25732
  // src/wiki/enrich.ts
24514
25733
  var exports_enrich2 = {};
24515
25734
  __export(exports_enrich2, {
24516
- wikiEnrich: () => wikiEnrich,
24517
- validateEnrichedMarkdown: () => validateEnrichedMarkdown,
24518
- setFrontmatterStatus: () => setFrontmatterStatus,
24519
- selectPages: () => selectPages,
24520
- saveResumeState: () => saveResumeState,
24521
- resumeStatePath: () => resumeStatePath,
24522
- resolveEnrichProviderModel: () => resolveEnrichProviderModel,
24523
- repairEnrichedFrontmatter: () => repairEnrichedFrontmatter,
24524
- planWikiEnrich: () => planWikiEnrich,
24525
- parseBatchResponse: () => parseBatchResponse,
24526
- mapPool: () => mapPool,
24527
- loadResumeState: () => loadResumeState,
24528
- isWikiEnrichIntent: () => isWikiEnrichIntent,
24529
- hasYamlFrontmatter: () => hasYamlFrontmatter,
24530
- hasCredential: () => hasCredential,
24531
- groupLightPagesIntoBatches: () => groupLightPagesIntoBatches,
24532
- extractYamlFrontmatterBlock: () => extractYamlFrontmatterBlock,
24533
- ensureWikiFrontmatter: () => ensureWikiFrontmatter,
24534
- defaultEnrichProgress: () => defaultEnrichProgress,
24535
- buildBatchUserPrompt: () => buildBatchUserPrompt,
24536
- batchGroupKey: () => batchGroupKey,
24537
- MAX_CONCURRENCY: () => MAX_CONCURRENCY,
25735
+ DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY,
24538
25736
  DEFAULT_MAX_OUTPUT_TOKENS: () => DEFAULT_MAX_OUTPUT_TOKENS,
24539
- DEFAULT_CONCURRENCY: () => DEFAULT_CONCURRENCY
25737
+ MAX_CONCURRENCY: () => MAX_CONCURRENCY,
25738
+ batchGroupKey: () => batchGroupKey,
25739
+ buildBatchUserPrompt: () => buildBatchUserPrompt,
25740
+ defaultEnrichProgress: () => defaultEnrichProgress,
25741
+ ensureWikiFrontmatter: () => ensureWikiFrontmatter,
25742
+ extractYamlFrontmatterBlock: () => extractYamlFrontmatterBlock,
25743
+ groupLightPagesIntoBatches: () => groupLightPagesIntoBatches,
25744
+ hasCredential: () => hasCredential,
25745
+ hasYamlFrontmatter: () => hasYamlFrontmatter,
25746
+ isWikiEnrichIntent: () => isWikiEnrichIntent,
25747
+ loadResumeState: () => loadResumeState,
25748
+ mapPool: () => mapPool,
25749
+ parseBatchResponse: () => parseBatchResponse,
25750
+ planWikiEnrich: () => planWikiEnrich,
25751
+ repairEnrichedFrontmatter: () => repairEnrichedFrontmatter,
25752
+ resolveEnrichProviderModel: () => resolveEnrichProviderModel,
25753
+ resumeStatePath: () => resumeStatePath,
25754
+ saveResumeState: () => saveResumeState,
25755
+ selectPages: () => selectPages,
25756
+ setFrontmatterStatus: () => setFrontmatterStatus,
25757
+ validateEnrichedMarkdown: () => validateEnrichedMarkdown,
25758
+ wikiEnrich: () => wikiEnrich
24540
25759
  });
24541
25760
  import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
24542
25761
  import { readFile as readFile57, writeFile as writeFile36 } from "fs/promises";
@@ -25588,9 +26807,9 @@ var init_enrich2 = __esm(() => {
25588
26807
  // src/sync/hooks.ts
25589
26808
  var exports_hooks = {};
25590
26809
  __export(exports_hooks, {
25591
- uninstallSyncHooks: () => uninstallSyncHooks,
26810
+ SYNC_HOOKS: () => SYNC_HOOKS,
25592
26811
  installSyncHooks: () => installSyncHooks,
25593
- SYNC_HOOKS: () => SYNC_HOOKS
26812
+ uninstallSyncHooks: () => uninstallSyncHooks
25594
26813
  });
25595
26814
  import { chmod as chmod3, mkdir as mkdir42, readFile as readFile59, writeFile as writeFile38 } from "fs/promises";
25596
26815
  import path111 from "path";
@@ -25773,8 +26992,8 @@ var init_reflect = __esm(() => {
25773
26992
  // src/lib/narrate.ts
25774
26993
  var exports_narrate = {};
25775
26994
  __export(exports_narrate, {
25776
- narrate: () => narrate,
25777
- formatMissingCredentialHint: () => formatMissingCredentialHint
26995
+ formatMissingCredentialHint: () => formatMissingCredentialHint,
26996
+ narrate: () => narrate
25778
26997
  });
25779
26998
  function formatMissingCredentialHint(provider) {
25780
26999
  const env = envWithSavedApiKeys(process.env);
@@ -26206,9 +27425,9 @@ var init_memory = __esm(() => {
26206
27425
  // src/lib/contained-path.ts
26207
27426
  var exports_contained_path = {};
26208
27427
  __export(exports_contained_path, {
26209
- resolveProjectRoot: () => resolveProjectRoot2,
27428
+ resolveContainedPath: () => resolveContainedPath,
26210
27429
  resolveContainedPathSync: () => resolveContainedPathSync,
26211
- resolveContainedPath: () => resolveContainedPath
27430
+ resolveProjectRoot: () => resolveProjectRoot2
26212
27431
  });
26213
27432
  import { realpath as realpath3 } from "fs/promises";
26214
27433
  import { existsSync as existsSync21, realpathSync as realpathSync2 } from "fs";
@@ -32398,10 +33617,72 @@ var GENERIC_RUNTIME = {
32398
33617
  validate: mcpValidate("generic"),
32399
33618
  hasManaged: mcpHasManaged
32400
33619
  };
33620
+ function readVscodeServers(settings) {
33621
+ return typeof settings.servers === "object" && settings.servers !== null && !Array.isArray(settings.servers) ? { ...settings.servers } : {};
33622
+ }
33623
+ function vscodeManagedEntry(projectRoot) {
33624
+ return {
33625
+ type: "stdio",
33626
+ ...mcpServerEntry(projectRoot),
33627
+ [MCP_MANAGED_KEY]: MCP_MANAGED_SENTINEL
33628
+ };
33629
+ }
33630
+ function vscodeMerge(settings, projectRoot) {
33631
+ const servers = readVscodeServers(settings);
33632
+ servers[MCP_SERVER_NAME] = vscodeManagedEntry(projectRoot);
33633
+ settings.servers = servers;
33634
+ return settings;
33635
+ }
33636
+ function vscodeStrip(settings) {
33637
+ if (typeof settings.servers !== "object" || settings.servers === null || Array.isArray(settings.servers)) {
33638
+ return settings;
33639
+ }
33640
+ const servers = { ...settings.servers };
33641
+ if (isManagedEntry(servers[MCP_SERVER_NAME])) {
33642
+ delete servers[MCP_SERVER_NAME];
33643
+ }
33644
+ if (Object.keys(servers).length > 0)
33645
+ settings.servers = servers;
33646
+ else
33647
+ delete settings.servers;
33648
+ return settings;
33649
+ }
33650
+ function vscodeValidate(settings) {
33651
+ const errors = [];
33652
+ const servers = settings.servers;
33653
+ if (typeof servers !== "object" || servers === null || Array.isArray(servers)) {
33654
+ errors.push("vscode: servers is missing or not an object");
33655
+ return errors;
33656
+ }
33657
+ const entry = servers[MCP_SERVER_NAME];
33658
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
33659
+ errors.push(`vscode: missing servers.${MCP_SERVER_NAME} entry`);
33660
+ return errors;
33661
+ }
33662
+ if (entry.type !== "stdio") {
33663
+ errors.push(`vscode: servers.${MCP_SERVER_NAME}.type must be "stdio"`);
33664
+ }
33665
+ if (entry.command !== MCP_SERVER_COMMAND) {
33666
+ errors.push(`vscode: servers.${MCP_SERVER_NAME}.command must be "${MCP_SERVER_COMMAND}"`);
33667
+ }
33668
+ return errors;
33669
+ }
33670
+ function vscodeHasManaged(settings) {
33671
+ return isManagedEntry(readVscodeServers(settings)[MCP_SERVER_NAME]);
33672
+ }
33673
+ var VSCODE_RUNTIME = {
33674
+ id: "vscode",
33675
+ settingsPath: (root) => path40.join(root, ".vscode", "mcp.json"),
33676
+ merge: vscodeMerge,
33677
+ strip: vscodeStrip,
33678
+ validate: vscodeValidate,
33679
+ hasManaged: vscodeHasManaged
33680
+ };
32401
33681
  var MCP_CLIENT_RUNTIMES = [
32402
33682
  CURSOR_RUNTIME,
32403
33683
  CLAUDE_RUNTIME2,
32404
33684
  OPENCODE_RUNTIME,
33685
+ VSCODE_RUNTIME,
32405
33686
  GENERIC_RUNTIME
32406
33687
  ];
32407
33688
  var ALL_RUNTIME_IDS = ["cursor", "claude", "opencode"];
@@ -32499,16 +33780,18 @@ protocol adapter \u2014 it defines no new module logic.
32499
33780
  requires \`http.enabled=true\` in this module's manifest entry).
32500
33781
  - \`keryx mcp serve --cwd <project-root>\` \u2014 expose a specific project,
32501
33782
  independent of the MCP client's launch directory.
32502
- - \`keryx mcp install --runtime <cursor|claude|opencode|generic|all> [--dry-run]\` \u2014
33783
+ - \`keryx mcp install --runtime <cursor|claude|opencode|vscode|generic|all> [--dry-run]\` \u2014
32503
33784
  wire this project into an editor/agent: writes a project-local client
32504
33785
  config (cursor \u2192 \`.cursor/mcp.json\`, claude \u2192 \`.mcp.json\`, opencode \u2192
32505
- \`opencode.json\`) and sets \`modules.mcp.enabled=true\`. \`--dry-run\`
32506
- prints the change without writing anything. This is the command to run
32507
- when a user asks to "connect" or "enable" MCP for this project \u2014 it is
32508
- the full, real setup step; hand-editing a client config file directly is
32509
- unnecessary and skips setting \`modules.mcp.enabled\`. \`all\` expands to
32510
- cursor + claude + opencode.
32511
- - \`keryx mcp uninstall --runtime <cursor|claude|opencode|generic|all>\` \u2014
33786
+ \`opencode.json\`, vscode \u2192 \`.vscode/mcp.json\`) and sets
33787
+ \`modules.mcp.enabled=true\`. \`--dry-run\` prints the change without
33788
+ writing anything. This is the command to run when a user asks to
33789
+ "connect" or "enable" MCP for this project \u2014 it is the full, real setup
33790
+ step; hand-editing a client config file directly is unnecessary and skips
33791
+ setting \`modules.mcp.enabled\`. \`all\` expands to cursor + claude +
33792
+ opencode; \`vscode\` is opt-in only (not bundled into \`all\`) \u2014 request it
33793
+ explicitly with \`--runtime vscode\`.
33794
+ - \`keryx mcp uninstall --runtime <cursor|claude|opencode|vscode|generic|all>\` \u2014
32512
33795
  remove the managed client config again.
32513
33796
  - **codex CLI**: not a \`--runtime\` here \u2014 codex's client config is a single
32514
33797
  GLOBAL \`~/.codex/config.toml\`, not a project-local file, and it already
@@ -32790,7 +34073,14 @@ skill's Reporting section).
32790
34073
  }
32791
34074
 
32792
34075
  // src/commands/init.ts
32793
- var MCP_INIT_RUNTIMES = ["cursor", "claude", "opencode", "generic", "skip"];
34076
+ var MCP_INIT_RUNTIMES = [
34077
+ "cursor",
34078
+ "claude",
34079
+ "opencode",
34080
+ "vscode",
34081
+ "generic",
34082
+ "skip"
34083
+ ];
32794
34084
  async function initCommand(args) {
32795
34085
  const options = parseInitArgs(args);
32796
34086
  if (options.help) {
@@ -35962,7 +37252,7 @@ the derived layers in step.
35962
37252
  init_args();
35963
37253
  init_fs();
35964
37254
  init_json();
35965
- import { readdir as readdir17, readFile as readFile64 } from "fs/promises";
37255
+ import { readdir as readdir18, readFile as readFile64 } from "fs/promises";
35966
37256
  import path120 from "path";
35967
37257
 
35968
37258
  // src/gdskills/contracts.ts
@@ -36094,7 +37384,7 @@ async function validateValue(value, schema, valuePath, errors, rootSchema, schem
36094
37384
  });
36095
37385
  }
36096
37386
  }
36097
- if (isPlainObject6(value)) {
37387
+ if (isPlainObject8(value)) {
36098
37388
  const required2 = schema.required ?? [];
36099
37389
  for (const key of required2) {
36100
37390
  if (!(key in value)) {
@@ -36114,7 +37404,7 @@ async function validateValue(value, schema, valuePath, errors, rootSchema, schem
36114
37404
  path: `${valuePath}.${key}`,
36115
37405
  message: "Additional property is not allowed"
36116
37406
  });
36117
- } else if (isPlainObject6(schema.additionalProperties)) {
37407
+ } else if (isPlainObject8(schema.additionalProperties)) {
36118
37408
  await validateValue(nestedValue, schema.additionalProperties, `${valuePath}.${key}`, errors, rootSchema, schemaCache);
36119
37409
  }
36120
37410
  }
@@ -36173,11 +37463,11 @@ function matchesType3(value, type) {
36173
37463
  if (entry === "integer")
36174
37464
  return Number.isInteger(value);
36175
37465
  if (entry === "object")
36176
- return isPlainObject6(value);
37466
+ return isPlainObject8(value);
36177
37467
  return typeof value === entry;
36178
37468
  });
36179
37469
  }
36180
- function isPlainObject6(value) {
37470
+ function isPlainObject8(value) {
36181
37471
  return typeof value === "object" && value !== null && !Array.isArray(value);
36182
37472
  }
36183
37473
  function formatType(type) {
@@ -36594,7 +37884,7 @@ function unique(values) {
36594
37884
  // src/gdskills/export.ts
36595
37885
  init_fs();
36596
37886
  init_json();
36597
- import { copyFile as copyFile3, mkdir as mkdir45, readdir as readdir15, writeFile as writeFile40 } from "fs/promises";
37887
+ import { copyFile as copyFile3, mkdir as mkdir45, readdir as readdir16, writeFile as writeFile40 } from "fs/promises";
36598
37888
  import path117 from "path";
36599
37889
 
36600
37890
  // src/gdskills/resolve.ts
@@ -36644,7 +37934,7 @@ async function normalizePackagePath(candidate) {
36644
37934
  // src/gdskills/export-plugin.ts
36645
37935
  init_fs();
36646
37936
  init_json();
36647
- import { copyFile as copyFile2, mkdir as mkdir44, readdir as readdir14, readFile as readFile62, writeFile as writeFile39 } from "fs/promises";
37937
+ import { copyFile as copyFile2, mkdir as mkdir44, readdir as readdir15, readFile as readFile62, writeFile as writeFile39 } from "fs/promises";
36648
37938
  import path116 from "path";
36649
37939
  var SAFE_DIRS = ["references", "templates", "assets", "scripts"];
36650
37940
  function skillTitle(skillMd, fallback) {
@@ -36666,7 +37956,7 @@ function skillVersion(skillMd) {
36666
37956
  return skillMd.match(/^Version:\s*(.+)$/m)?.[1]?.trim() ?? "0.1.0";
36667
37957
  }
36668
37958
  async function listFiles(root) {
36669
- const entries = await readdir14(root, { withFileTypes: true });
37959
+ const entries = await readdir15(root, { withFileTypes: true });
36670
37960
  const files = [];
36671
37961
  for (const entry of entries) {
36672
37962
  const full = path116.join(root, entry.name);
@@ -36856,7 +38146,7 @@ async function copyDirectoryIfExists(sourceDir, targetDir) {
36856
38146
  }
36857
38147
  }
36858
38148
  async function listFiles2(root) {
36859
- const entries = await readdir15(root, { withFileTypes: true });
38149
+ const entries = await readdir16(root, { withFileTypes: true });
36860
38150
  const files = [];
36861
38151
  for (const entry of entries) {
36862
38152
  const entryPath = path117.join(root, entry.name);
@@ -36877,7 +38167,7 @@ function inferModuleFromPackageRoot(packageRoot) {
36877
38167
 
36878
38168
  // src/gdskills/sync.ts
36879
38169
  init_fs();
36880
- import { copyFile as copyFile4, mkdir as mkdir46, readdir as readdir16, writeFile as writeFile41 } from "fs/promises";
38170
+ import { copyFile as copyFile4, mkdir as mkdir46, readdir as readdir17, writeFile as writeFile41 } from "fs/promises";
36881
38171
  import path118 from "path";
36882
38172
  async function syncRuntimeSkills(projectRoot, options) {
36883
38173
  const metaprojectRoot = path118.join(projectRoot, ".metaproject");
@@ -36942,7 +38232,7 @@ function validateSyncTarget(projectRoot, targetRoot) {
36942
38232
  }
36943
38233
  }
36944
38234
  async function listSkillArtifactDirs(sourceRoot) {
36945
- const entries = await readdir16(sourceRoot, { withFileTypes: true });
38235
+ const entries = await readdir17(sourceRoot, { withFileTypes: true });
36946
38236
  const dirs = [];
36947
38237
  for (const entry of entries) {
36948
38238
  if (!entry.isDirectory()) {
@@ -36972,7 +38262,7 @@ async function copyDirectory(sourceDir, targetDir) {
36972
38262
  }
36973
38263
  }
36974
38264
  async function listFiles3(root) {
36975
- const entries = await readdir16(root, { withFileTypes: true });
38265
+ const entries = await readdir17(root, { withFileTypes: true });
36976
38266
  const files = [];
36977
38267
  for (const entry of entries) {
36978
38268
  const entryPath = path118.join(root, entry.name);
@@ -38150,7 +39440,7 @@ async function listJsonFiles(root) {
38150
39440
  if (!await pathExists(root)) {
38151
39441
  return [];
38152
39442
  }
38153
- const entries = await readdir17(root, { withFileTypes: true });
39443
+ const entries = await readdir18(root, { withFileTypes: true });
38154
39444
  return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path120.join(root, entry.name));
38155
39445
  }
38156
39446
  function countVerificationStatuses(reports) {
@@ -38326,14 +39616,14 @@ init_service6();
38326
39616
  // src/health/history.ts
38327
39617
  init_fs();
38328
39618
  init_util();
38329
- import { readdir as readdir18, readFile as readFile65 } from "fs/promises";
39619
+ import { readdir as readdir19, readFile as readFile65 } from "fs/promises";
38330
39620
  import path121 from "path";
38331
39621
  async function loadHistory(cwd, limit = 20) {
38332
39622
  const dir = path121.join(dataRoot2(cwd), "history");
38333
39623
  if (!await pathExists(dir)) {
38334
39624
  return [];
38335
39625
  }
38336
- const files = (await readdir18(dir)).filter((file) => file.endsWith(".json")).sort();
39626
+ const files = (await readdir19(dir)).filter((file) => file.endsWith(".json")).sort();
38337
39627
  const points = [];
38338
39628
  for (const file of files.slice(-limit)) {
38339
39629
  try {
@@ -39320,7 +40610,7 @@ init_args();
39320
40610
  init_validator();
39321
40611
  init_fs();
39322
40612
  init_store2();
39323
- import { mkdir as mkdir48, readFile as readFile66, readdir as readdir19 } from "fs/promises";
40613
+ import { mkdir as mkdir48, readFile as readFile66, readdir as readdir20 } from "fs/promises";
39324
40614
  import path124 from "path";
39325
40615
 
39326
40616
  // src/review/types.ts
@@ -39748,7 +41038,7 @@ async function resolveReviewPackagePath(cwd, ref) {
39748
41038
  if (!await pathExists(reviewsDir)) {
39749
41039
  continue;
39750
41040
  }
39751
- for (const entry of await readdir19(reviewsDir, { withFileTypes: true })) {
41041
+ for (const entry of await readdir20(reviewsDir, { withFileTypes: true })) {
39752
41042
  if (entry.isDirectory() && entry.name === ref) {
39753
41043
  return path124.join(reviewsDir, entry.name);
39754
41044
  }
@@ -40135,7 +41425,7 @@ var SCHEMA_REGISTRY2 = {
40135
41425
 
40136
41426
  // src/standard/validate.ts
40137
41427
  var DATE_TIME_PATTERN3 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
40138
- function isPlainObject7(value) {
41428
+ function isPlainObject9(value) {
40139
41429
  return typeof value === "object" && value !== null && !Array.isArray(value);
40140
41430
  }
40141
41431
  function matchesType4(value, type) {
@@ -40148,7 +41438,7 @@ function matchesType4(value, type) {
40148
41438
  if (entry === "integer")
40149
41439
  return Number.isInteger(value);
40150
41440
  if (entry === "object")
40151
- return isPlainObject7(value);
41441
+ return isPlainObject9(value);
40152
41442
  return typeof value === entry;
40153
41443
  });
40154
41444
  }
@@ -40223,7 +41513,7 @@ function walk4(value, schema, valuePath, rootSchema, errors) {
40223
41513
  errors.push({ path: valuePath, message: "Expected array items to be unique" });
40224
41514
  }
40225
41515
  }
40226
- if (isPlainObject7(value)) {
41516
+ if (isPlainObject9(value)) {
40227
41517
  for (const key of schema.required ?? []) {
40228
41518
  if (!(key in value)) {
40229
41519
  errors.push({ path: `${valuePath}.${key}`, message: "Missing required property" });
@@ -40236,7 +41526,7 @@ function walk4(value, schema, valuePath, rootSchema, errors) {
40236
41526
  walk4(nested, nestedSchema, `${valuePath}.${key}`, rootSchema, errors);
40237
41527
  } else if (schema.additionalProperties === false) {
40238
41528
  errors.push({ path: `${valuePath}.${key}`, message: "Additional property is not allowed" });
40239
- } else if (isPlainObject7(schema.additionalProperties)) {
41529
+ } else if (isPlainObject9(schema.additionalProperties)) {
40240
41530
  walk4(nested, schema.additionalProperties, `${valuePath}.${key}`, rootSchema, errors);
40241
41531
  }
40242
41532
  }
@@ -40319,7 +41609,7 @@ async function validateWorkspace2(cwd) {
40319
41609
  errors.push(issue("module-schema", `module "${key}" ${schemaError.path.replace(/^\$\.?/, "") || "(root)"}: ${schemaError.message}`, `Fix the modules.${key} entry in metaproject.json.`));
40320
41610
  }
40321
41611
  }
40322
- if (isPlainObject7(manifest.paths)) {
41612
+ if (isPlainObject9(manifest.paths)) {
40323
41613
  for (const [key, value] of Object.entries(manifest.paths)) {
40324
41614
  if (typeof value !== "string") {
40325
41615
  continue;
@@ -40427,7 +41717,7 @@ async function runCapabilities(cwd) {
40427
41717
  init_fs();
40428
41718
  init_json();
40429
41719
  import path128 from "path";
40430
- import { readdir as readdir20 } from "fs/promises";
41720
+ import { readdir as readdir21 } from "fs/promises";
40431
41721
  function llmsPath(cwd) {
40432
41722
  return path128.join(cwd, ".metaproject", "llms.txt");
40433
41723
  }
@@ -40480,7 +41770,7 @@ async function collectArtifactIndex(cwd) {
40480
41770
  const walk5 = async (dir) => {
40481
41771
  let entries;
40482
41772
  try {
40483
- entries = await readdir20(dir, { withFileTypes: true });
41773
+ entries = await readdir21(dir, { withFileTypes: true });
40484
41774
  } catch {
40485
41775
  return;
40486
41776
  }
@@ -41251,7 +42541,7 @@ Usage:
41251
42541
  }
41252
42542
 
41253
42543
  // src/commands/security.ts
41254
- import { mkdir as mkdir51, readdir as readdir21, readFile as readFile72, writeFile as writeFile45 } from "fs/promises";
42544
+ import { mkdir as mkdir51, readdir as readdir22, readFile as readFile72, writeFile as writeFile45 } from "fs/promises";
41255
42545
  import path131 from "path";
41256
42546
  import { fileURLToPath as fileURLToPath6 } from "url";
41257
42547
  init_args();
@@ -41718,7 +43008,7 @@ async function collectManifestFiles(target) {
41718
43008
  }
41719
43009
  let entries;
41720
43010
  try {
41721
- entries = await readdir21(target, { withFileTypes: true });
43011
+ entries = await readdir22(target, { withFileTypes: true });
41722
43012
  } catch {
41723
43013
  return [target];
41724
43014
  }
@@ -42574,10 +43864,10 @@ async function resolveLedgerState(ledger, checkpointPath, verifier) {
42574
43864
  }
42575
43865
  var policyRefPattern = /^\.\/.+/;
42576
43866
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
42577
- var asString4 = (value) => typeof value === "string" && value.length > 0;
43867
+ var asString6 = (value) => typeof value === "string" && value.length > 0;
42578
43868
  var isBaselineArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "deterministic-baseline" && isImmutableVersion2(artifact.version) && artifact.selection === "eligible-ids-in-input-order";
42579
- var isCandidateArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "offline-selection-advisor" && isImmutableVersion2(artifact.version) && asString4(artifact.output) && typeof artifact.mutations === "boolean";
42580
- var isEvaluationReport = (report) => report.schemaVersion === "1.0" && asString4(report.status) && asString4(report.baselineVersion) && asString4(report.baselineDigest) && reportHashPattern.test(report.baselineDigest) && asString4(report.candidateVersion) && asString4(report.candidateDigest) && reportHashPattern.test(report.candidateDigest) && asString4(report.corpusVersion) && asString4(report.corpusDigest) && reportHashPattern.test(report.corpusDigest) && asString4(report.reportDigest) && reportHashPattern.test(report.reportDigest) && asString4(report.sandboxProfileDigest) && reportHashPattern.test(report.sandboxProfileDigest) && isRecord3(report.train) && typeof report.train.status === "string" && typeof report.train.cases === "number" && isRecord3(report.holdout) && typeof report.holdout.status === "string" && typeof report.holdout.cases === "number" && isRecord3(report.adversarial) && typeof report.adversarial.status === "string" && typeof report.adversarial.cases === "number" && Array.isArray(report.candidateSelectedIds) && Array.isArray(report.reasons);
43869
+ var isCandidateArtifact = (artifact) => artifact.schemaVersion === "1.0" && artifact.kind === "offline-selection-advisor" && isImmutableVersion2(artifact.version) && asString6(artifact.output) && typeof artifact.mutations === "boolean";
43870
+ var isEvaluationReport = (report) => report.schemaVersion === "1.0" && asString6(report.status) && asString6(report.baselineVersion) && asString6(report.baselineDigest) && reportHashPattern.test(report.baselineDigest) && asString6(report.candidateVersion) && asString6(report.candidateDigest) && reportHashPattern.test(report.candidateDigest) && asString6(report.corpusVersion) && asString6(report.corpusDigest) && reportHashPattern.test(report.corpusDigest) && asString6(report.reportDigest) && reportHashPattern.test(report.reportDigest) && asString6(report.sandboxProfileDigest) && reportHashPattern.test(report.sandboxProfileDigest) && isRecord3(report.train) && typeof report.train.status === "string" && typeof report.train.cases === "number" && isRecord3(report.holdout) && typeof report.holdout.status === "string" && typeof report.holdout.cases === "number" && isRecord3(report.adversarial) && typeof report.adversarial.status === "string" && typeof report.adversarial.cases === "number" && Array.isArray(report.candidateSelectedIds) && Array.isArray(report.reasons);
42581
43871
  var isConfigRecord = (value) => typeof value.enabled === "boolean" && typeof value.killSwitch === "boolean";
42582
43872
  async function readPinnedJson(input2) {
42583
43873
  const ref = await resolveWorkspaceReference({ workspaceRoot: input2.workspaceRoot, kind: "artifact", uri: input2.uri });
@@ -42627,7 +43917,7 @@ async function resolvePolicySelection(workspaceRoot, canonicalFallback) {
42627
43917
  const corpusDigest = configJson.corpusDigest;
42628
43918
  const evaluationRef = configJson.evaluationReportRef;
42629
43919
  const evaluationDigest = configJson.evaluationDigest;
42630
- if (!asString4(candidateRef) || !asString4(candidateDigest) || !asString4(baselineRef) || !asString4(baselineDigest) || !asString4(corpusRef) || !asString4(corpusDigest) || !asString4(evaluationRef) || !asString4(evaluationDigest))
43920
+ if (!asString6(candidateRef) || !asString6(candidateDigest) || !asString6(baselineRef) || !asString6(baselineDigest) || !asString6(corpusRef) || !asString6(corpusDigest) || !asString6(evaluationRef) || !asString6(evaluationDigest))
42631
43921
  return canonicalFallback;
42632
43922
  if (!workspacePathPattern3.test(candidateRef) || !workspacePathPattern3.test(baselineRef) || !workspacePathPattern3.test(corpusRef) || !workspacePathPattern3.test(evaluationRef))
42633
43923
  return canonicalFallback;
@@ -42742,7 +44032,7 @@ async function diagnosePolicyReadiness(workspaceRoot) {
42742
44032
  const corpusDigest = configJson.corpusDigest;
42743
44033
  const evaluationRef = configJson.evaluationReportRef;
42744
44034
  const evaluationDigest = configJson.evaluationDigest;
42745
- if (!asString4(candidateRef) || !asString4(candidateDigest) || !asString4(baselineRef) || !asString4(baselineDigest) || !asString4(corpusRef) || !asString4(corpusDigest) || !asString4(evaluationRef) || !asString4(evaluationDigest)) {
44035
+ if (!asString6(candidateRef) || !asString6(candidateDigest) || !asString6(baselineRef) || !asString6(baselineDigest) || !asString6(corpusRef) || !asString6(corpusDigest) || !asString6(evaluationRef) || !asString6(evaluationDigest)) {
42746
44036
  fail("config-pins", "pins are present but not non-empty strings");
42747
44037
  return finalize(true, enabled, killSwitch);
42748
44038
  }
@@ -43437,7 +44727,7 @@ function buildToolRegistry() {
43437
44727
  // src/mcp/resources.ts
43438
44728
  init_fs();
43439
44729
  import path135 from "path";
43440
- import { readdir as readdir22, readFile as readFile76, stat as stat7 } from "fs/promises";
44730
+ import { readdir as readdir23, readFile as readFile76, stat as stat7 } from "fs/promises";
43441
44731
  var URI_PREFIX = "metaproject://";
43442
44732
  function mimeForPath(filePath) {
43443
44733
  if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
@@ -43464,7 +44754,7 @@ async function walkFiles(root) {
43464
44754
  const out = [];
43465
44755
  let entries;
43466
44756
  try {
43467
- entries = await readdir22(root, { withFileTypes: true });
44757
+ entries = await readdir23(root, { withFileTypes: true });
43468
44758
  } catch {
43469
44759
  return [];
43470
44760
  }
@@ -43486,7 +44776,7 @@ async function listArtifacts(cwd) {
43486
44776
  const listings = [];
43487
44777
  let modules;
43488
44778
  try {
43489
- modules = await readdir22(base, { withFileTypes: true });
44779
+ modules = await readdir23(base, { withFileTypes: true });
43490
44780
  } catch {
43491
44781
  return [];
43492
44782
  }
@@ -43778,7 +45068,7 @@ async function mcpCommand(args2 = [], cwd = process.cwd()) {
43778
45068
  printMcpHelp();
43779
45069
  process.exitCode = 1;
43780
45070
  }
43781
- var RUNTIME_USAGE = `<cursor|claude|opencode|generic|all>`;
45071
+ var RUNTIME_USAGE = `<cursor|claude|opencode|vscode|generic|all>`;
43782
45072
  function parseRequestedRuntimes(args2, fallback) {
43783
45073
  const runtimeArg = optionValue(args2, "--runtime") ?? fallback;
43784
45074
  return runtimeArg.split(",").map((s) => s.trim()).filter(Boolean);
@@ -43862,7 +45152,7 @@ function printMcpHelp() {
43862
45152
  { flag: "--dry-run", desc: "install only: print the planned change and write nothing." }
43863
45153
  ]);
43864
45154
  heading("Notes");
43865
- console.log(` ${style.dim("`install` writes a project-local MCP client config (cursor \u2192 .cursor/mcp.json, claude \u2192 .mcp.json, opencode \u2192 opencode.json), sets modules.mcp.enabled=true, and prints a snippet for `generic`.")}`);
45155
+ console.log(` ${style.dim("`install` writes a project-local MCP client config (cursor \u2192 .cursor/mcp.json, claude \u2192 .mcp.json, opencode \u2192 opencode.json, vscode \u2192 .vscode/mcp.json), sets modules.mcp.enabled=true, and prints a snippet for `generic`. `vscode` is opt-in only \u2014 not included in `all`.")}`);
43866
45156
  console.log(` ${style.dim("Requires the optional @modelcontextprotocol/sdk to serve. Disabled by default (modules.mcp.enabled=false). `install` only probes the SDK \u2014 it never installs it or opens a network connection.")}`);
43867
45157
  }
43868
45158
 
@@ -45328,6 +46618,9 @@ function buildDefaultMaskProviders(openaiCompat) {
45328
46618
  out.push({ envKey: p.envKey, baseUrl: p.baseUrl });
45329
46619
  }
45330
46620
  out.push({ envKey: "ANTHROPIC_API_KEY", baseUrl: "https://api.anthropic.com" });
46621
+ out.push({ envKey: "OPENAI_API_KEY", baseUrl: "https://api.openai.com" });
46622
+ out.push({ envKey: "GEMINI_API_KEY", baseUrl: "https://generativelanguage.googleapis.com" });
46623
+ out.push({ envKey: "GOOGLE_API_KEY", baseUrl: "https://generativelanguage.googleapis.com" });
45331
46624
  return out;
45332
46625
  }
45333
46626
  function parseMaskMode(raw) {
@@ -46597,7 +47890,7 @@ async function buildApprovalContext(port, command) {
46597
47890
  import { randomUUID as randomUUID20 } from "crypto";
46598
47891
 
46599
47892
  // src/harness/tool/builtin/interactive-tools.ts
46600
- import { readdir as readdir23 } from "fs/promises";
47893
+ import { readdir as readdir24 } from "fs/promises";
46601
47894
  import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep } from "path";
46602
47895
  import { realpathSync as realpathSync4 } from "fs";
46603
47896
  var MAX_READ_BYTES = 20000;
@@ -46649,7 +47942,7 @@ function builtinReadOnlyTools(root) {
46649
47942
  return { output: `path escapes the project root: ${requested}`, isError: true };
46650
47943
  }
46651
47944
  try {
46652
- const entries = await readdir23(target, { withFileTypes: true });
47945
+ const entries = await readdir24(target, { withFileTypes: true });
46653
47946
  const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort();
46654
47947
  return { output: lines.length > 0 ? lines.join(`
46655
47948
  `) : "(empty)", isError: false };
@@ -50330,7 +51623,7 @@ function buildClaudeResumeArgv(sessionRef, message2, input2) {
50330
51623
  function asObject(value) {
50331
51624
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
50332
51625
  }
50333
- function asString5(value) {
51626
+ function asString7(value) {
50334
51627
  return typeof value === "string" ? value : undefined;
50335
51628
  }
50336
51629
  function asFiniteNumber(value) {
@@ -50343,16 +51636,16 @@ function truncate3(text, limit) {
50343
51636
  var CLAUDE_UNMAPPED_SYSTEM_SUBTYPES = ["hook_started", "hook_response"];
50344
51637
  var CLAUDE_UNMAPPED_LINE_TYPES = ["rate_limit_event"];
50345
51638
  function systemEvents(obj) {
50346
- const subtype = asString5(obj.subtype);
51639
+ const subtype = asString7(obj.subtype);
50347
51640
  if (subtype === "init") {
50348
- const sessionRef = asString5(obj.session_id);
51641
+ const sessionRef = asString7(obj.session_id);
50349
51642
  return [sessionRef === undefined ? { kind: "child_started" } : { kind: "child_started", sessionRef }];
50350
51643
  }
50351
51644
  if (subtype === "api_retry") {
50352
51645
  const attempt = asFiniteNumber(obj.attempt);
50353
51646
  const max = asFiniteNumber(obj.max_retries);
50354
51647
  const status = asFiniteNumber(obj.error_status);
50355
- const error2 = asString5(obj.error);
51648
+ const error2 = asString7(obj.error);
50356
51649
  const parts = [
50357
51650
  attempt === undefined ? "api retry" : `api retry ${attempt}${max === undefined ? "" : `/${max}`}`,
50358
51651
  status === undefined ? undefined : `status ${status}`,
@@ -50363,32 +51656,32 @@ function systemEvents(obj) {
50363
51656
  return [];
50364
51657
  }
50365
51658
  function toolResultDetail(content) {
50366
- const direct = asString5(content);
51659
+ const direct = asString7(content);
50367
51660
  if (direct !== undefined)
50368
51661
  return truncate3(direct, TOOL_DETAIL_LIMIT);
50369
51662
  if (!Array.isArray(content))
50370
51663
  return;
50371
- const text = content.map((entry) => asString5(asObject(entry)?.text)).filter((entry) => entry !== undefined).join(`
51664
+ const text = content.map((entry) => asString7(asObject(entry)?.text)).filter((entry) => entry !== undefined).join(`
50372
51665
  `);
50373
51666
  return text.length === 0 ? undefined : truncate3(text, TOOL_DETAIL_LIMIT);
50374
51667
  }
50375
51668
  function assistantBlockEvent(block) {
50376
- switch (asString5(block.type)) {
51669
+ switch (asString7(block.type)) {
50377
51670
  case "tool_use": {
50378
- const name = asString5(block.name) ?? "unknown";
51671
+ const name = asString7(block.name) ?? "unknown";
50379
51672
  const detail = block.input === undefined ? undefined : truncate3(JSON.stringify(block.input), TOOL_DETAIL_LIMIT);
50380
51673
  return detail === undefined ? { kind: "tool_call", name } : { kind: "tool_call", name, detail };
50381
51674
  }
50382
51675
  case "text":
50383
- return { kind: "assistant_text", text: asString5(block.text) ?? "" };
51676
+ return { kind: "assistant_text", text: asString7(block.text) ?? "" };
50384
51677
  case "thinking":
50385
- return { kind: "thinking", text: asString5(block.thinking) ?? asString5(block.text) ?? "" };
51678
+ return { kind: "thinking", text: asString7(block.thinking) ?? asString7(block.text) ?? "" };
50386
51679
  default:
50387
51680
  return;
50388
51681
  }
50389
51682
  }
50390
51683
  function userBlockEvent(block) {
50391
- if (asString5(block.type) !== "tool_result")
51684
+ if (asString7(block.type) !== "tool_result")
50392
51685
  return;
50393
51686
  const detail = toolResultDetail(block.content);
50394
51687
  return detail === undefined ? { kind: "tool_result" } : { kind: "tool_result", detail };
@@ -50426,14 +51719,14 @@ function usageEvent(obj) {
50426
51719
  };
50427
51720
  }
50428
51721
  function describeFailure(obj) {
50429
- const subtype = asString5(obj.subtype) ?? "unknown";
51722
+ const subtype = asString7(obj.subtype) ?? "unknown";
50430
51723
  const parts = [`result.subtype "${subtype}"`];
50431
- const text = asString5(obj.result);
51724
+ const text = asString7(obj.result);
50432
51725
  if (text !== undefined && text.length > 0)
50433
51726
  parts.push(truncate3(text, TOOL_DETAIL_LIMIT));
50434
51727
  const errors = obj.errors;
50435
51728
  if (Array.isArray(errors)) {
50436
- const flat = errors.map((entry) => asString5(entry)).filter((entry) => entry !== undefined);
51729
+ const flat = errors.map((entry) => asString7(entry)).filter((entry) => entry !== undefined);
50437
51730
  if (flat.length > 0)
50438
51731
  parts.push(truncate3(flat.join("; "), TOOL_DETAIL_LIMIT));
50439
51732
  }
@@ -50443,10 +51736,10 @@ function describeFailure(obj) {
50443
51736
  return parts.join(" \u2014 ");
50444
51737
  }
50445
51738
  function resultEvents(obj) {
50446
- const subtype = asString5(obj.subtype);
51739
+ const subtype = asString7(obj.subtype);
50447
51740
  const succeeded = subtype === undefined ? obj.is_error === false : subtype === "success";
50448
51741
  const terminal = succeeded ? (() => {
50449
- const text = asString5(obj.result);
51742
+ const text = asString7(obj.result);
50450
51743
  return text === undefined ? { kind: "child_finished" } : { kind: "child_finished", text };
50451
51744
  })() : { kind: "child_failed", message: describeFailure(obj) };
50452
51745
  const usage = usageEvent(obj);
@@ -50465,7 +51758,7 @@ function parseClaudeEvents(line) {
50465
51758
  const obj = asObject(parsed);
50466
51759
  if (obj === undefined)
50467
51760
  return [];
50468
- switch (asString5(obj.type)) {
51761
+ switch (asString7(obj.type)) {
50469
51762
  case "system":
50470
51763
  return systemEvents(obj);
50471
51764
  case "assistant":
@@ -50495,13 +51788,13 @@ function isRecognisedClaudeLine(line) {
50495
51788
  const obj = asObject(parsed);
50496
51789
  if (obj === undefined)
50497
51790
  return false;
50498
- const type = asString5(obj.type);
51791
+ const type = asString7(obj.type);
50499
51792
  if (type === undefined)
50500
51793
  return false;
50501
51794
  if (CLAUDE_UNMAPPED_LINE_TYPES.includes(type))
50502
51795
  return true;
50503
51796
  if (type === "system") {
50504
- const subtype = asString5(obj.subtype) ?? "";
51797
+ const subtype = asString7(obj.subtype) ?? "";
50505
51798
  return subtype === "init" || subtype === "api_retry" || CLAUDE_UNMAPPED_SYSTEM_SUBTYPES.includes(subtype);
50506
51799
  }
50507
51800
  return type === "assistant" || type === "user" || type === "result";
@@ -50595,9 +51888,9 @@ function parseCodexEvents(line) {
50595
51888
  const record = readJsonObject(line);
50596
51889
  if (record === undefined)
50597
51890
  return [];
50598
- switch (asString6(record.type)) {
51891
+ switch (asString8(record.type)) {
50599
51892
  case "thread.started": {
50600
- const threadId = asString6(record.thread_id);
51893
+ const threadId = asString8(record.thread_id);
50601
51894
  return [threadId === undefined ? { kind: "child_started" } : { kind: "child_started", sessionRef: threadId }];
50602
51895
  }
50603
51896
  case "turn.started":
@@ -50615,7 +51908,7 @@ function parseCodexEvents(line) {
50615
51908
  case "turn.failed":
50616
51909
  return [{ kind: "child_failed", message: parseFailureMessage(record.error) }];
50617
51910
  case "error":
50618
- return [{ kind: "retry", message: asString6(record.message) ?? "codex reported a non-terminal error" }];
51911
+ return [{ kind: "retry", message: asString8(record.message) ?? "codex reported a non-terminal error" }];
50619
51912
  default:
50620
51913
  return [];
50621
51914
  }
@@ -50636,7 +51929,7 @@ function isRecognisedCodexLine(line) {
50636
51929
  const record = readJsonObject(line);
50637
51930
  if (record === undefined)
50638
51931
  return false;
50639
- const type = asString6(record.type);
51932
+ const type = asString8(record.type);
50640
51933
  return type !== undefined && RECOGNISED_TYPES.has(type);
50641
51934
  }
50642
51935
  var NARRATED_FAILURE_LINE = /^\s*(error\b|usage:)/i;
@@ -50696,25 +51989,25 @@ function readJsonObject(line) {
50696
51989
  return;
50697
51990
  }
50698
51991
  }
50699
- function asString6(value) {
51992
+ function asString8(value) {
50700
51993
  return typeof value === "string" && value.length > 0 ? value : undefined;
50701
51994
  }
50702
- function asNumber4(value) {
51995
+ function asNumber6(value) {
50703
51996
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
50704
51997
  }
50705
51998
  function parseCompletedItem(raw) {
50706
51999
  if (typeof raw !== "object" || raw === null)
50707
52000
  return [];
50708
52001
  const item = raw;
50709
- switch (asString6(item.type)) {
52002
+ switch (asString8(item.type)) {
50710
52003
  case "command_execution": {
50711
- const command = asString6(item.command);
52004
+ const command = asString8(item.command);
50712
52005
  return [
50713
52006
  command === undefined ? { kind: "tool_call", name: "command_execution" } : { kind: "tool_call", name: "command_execution", detail: command }
50714
52007
  ];
50715
52008
  }
50716
52009
  case "agent_message": {
50717
- const text = asString6(item.text);
52010
+ const text = asString8(item.text);
50718
52011
  return text === undefined ? [] : [{ kind: "assistant_text", text }];
50719
52012
  }
50720
52013
  default:
@@ -50725,8 +52018,8 @@ function parseUsage(raw) {
50725
52018
  if (typeof raw !== "object" || raw === null)
50726
52019
  return;
50727
52020
  const usage = raw;
50728
- const inputTokens = asNumber4(usage.input_tokens);
50729
- const outputTokens = asNumber4(usage.output_tokens);
52021
+ const inputTokens = asNumber6(usage.input_tokens);
52022
+ const outputTokens = asNumber6(usage.output_tokens);
50730
52023
  if (inputTokens === undefined && outputTokens === undefined)
50731
52024
  return;
50732
52025
  return {
@@ -50737,11 +52030,11 @@ function parseUsage(raw) {
50737
52030
  }
50738
52031
  function parseFailureMessage(raw) {
50739
52032
  if (typeof raw === "object" && raw !== null) {
50740
- const message2 = asString6(raw.message);
52033
+ const message2 = asString8(raw.message);
50741
52034
  if (message2 !== undefined)
50742
52035
  return message2;
50743
52036
  }
50744
- return asString6(raw) ?? "codex reported a failed turn without a message";
52037
+ return asString8(raw) ?? "codex reported a failed turn without a message";
50745
52038
  }
50746
52039
  function lastTerminalEvent(events) {
50747
52040
  for (let i = events.length - 1;i >= 0; i -= 1) {
@@ -51627,7 +52920,7 @@ import { spawnSync as spawnSync2 } from "child_process";
51627
52920
  // package.json
51628
52921
  var package_default = {
51629
52922
  name: "@mrciphersmith/keryx",
51630
- version: "0.2.49",
52923
+ version: "0.2.50",
51631
52924
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
51632
52925
  private: false,
51633
52926
  publishConfig: {
@@ -52888,7 +54181,7 @@ init_store3();
52888
54181
  init_proposal_lifecycle();
52889
54182
  init_workspace_service();
52890
54183
  import { randomUUID as randomUUID24 } from "crypto";
52891
- import { readdir as readdir24 } from "fs/promises";
54184
+ import { readdir as readdir25 } from "fs/promises";
52892
54185
  import path148 from "path";
52893
54186
 
52894
54187
  // src/sac/lifecycle-flag.ts
@@ -53019,7 +54312,7 @@ async function isSlateEngaged(dir) {
53019
54312
  if (await pathExists(path148.join(dir, "terminal-state.json")))
53020
54313
  return true;
53021
54314
  try {
53022
- const entries = await readdir24(path148.join(dir, "slate-archive"));
54315
+ const entries = await readdir25(path148.join(dir, "slate-archive"));
53023
54316
  return entries.length > 0;
53024
54317
  } catch {
53025
54318
  return false;
@@ -53047,7 +54340,7 @@ async function readNewestUnboundCandidate(dir) {
53047
54340
  const archiveDir = path148.join(dir, "slate-archive");
53048
54341
  let entries;
53049
54342
  try {
53050
- entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
54343
+ entries = (await readdir25(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
53051
54344
  } catch {
53052
54345
  return;
53053
54346
  }
@@ -53084,7 +54377,7 @@ async function readNewestWrapUpOutcome(dir) {
53084
54377
  const archiveDir = path148.join(dir, "slate-archive");
53085
54378
  let entries;
53086
54379
  try {
53087
- entries = (await readdir24(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
54380
+ entries = (await readdir25(archiveDir)).filter((name) => name.endsWith("-wrap-up-outcome.json"));
53088
54381
  } catch {
53089
54382
  return;
53090
54383
  }
@@ -54921,6 +56214,38 @@ init_providers();
54921
56214
  init_patch_risk();
54922
56215
  init_shell_config();
54923
56216
 
56217
+ // src/mcp-client/wire.ts
56218
+ var STANDARD_ELICITATION_PARAM_KEYS = new Set(["message", "requestedSchema"]);
56219
+
56220
+ // src/mcp-client/elicitation.ts
56221
+ init_command_risk();
56222
+ function extractCodexCommand(vendor) {
56223
+ const raw = vendor.codex_command;
56224
+ if (!Array.isArray(raw))
56225
+ return;
56226
+ return raw.every((entry) => typeof entry === "string") ? raw : undefined;
56227
+ }
56228
+ var SHELL_BASENAMES = new Set(["zsh", "bash", "sh", "ksh", "dash", "fish"]);
56229
+ var MCP_ELICITATION_TOOL_PREFIX = "mcp_elicitation:";
56230
+ function describeElicitationPrompt(tool, inputJson) {
56231
+ if (!tool.startsWith(MCP_ELICITATION_TOOL_PREFIX))
56232
+ return;
56233
+ let parsed;
56234
+ try {
56235
+ parsed = JSON.parse(inputJson);
56236
+ } catch {
56237
+ return { message: inputJson, command: undefined };
56238
+ }
56239
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
56240
+ return { message: inputJson, command: undefined };
56241
+ }
56242
+ const obj = parsed;
56243
+ const message2 = typeof obj.message === "string" && obj.message.trim().length > 0 ? obj.message : "codex is requesting approval for an action";
56244
+ const vendor = typeof obj.vendor === "object" && obj.vendor !== null ? obj.vendor : {};
56245
+ const command = extractCodexCommand(vendor)?.join(" ");
56246
+ return { message: message2, command };
56247
+ }
56248
+
54924
56249
  // src/lib/permission-mode-config.ts
54925
56250
  init_permission_mode();
54926
56251
  init_config_dir();
@@ -58556,6 +59881,275 @@ ${rows.join(`
58556
59881
  (none)`}
58557
59882
  `;
58558
59883
  }
59884
+ async function selectSearchProviderAndReport(controller, onSystem, providerId) {
59885
+ const result = await controller.select(providerId);
59886
+ if (!result.ok) {
59887
+ if (result.reason === "not-configured") {
59888
+ onSystem?.(`Cannot select '${providerId}': provider is not configured.
59889
+ `);
59890
+ } else if (result.reason === "not-connected") {
59891
+ onSystem?.(`Cannot select '${providerId}': provider is not connected (run /search-provider ${providerId} <params> to test).
59892
+ `);
59893
+ } else {
59894
+ onSystem?.(`Cannot select '${providerId}': ${result.reason}.
59895
+ `);
59896
+ }
59897
+ return;
59898
+ }
59899
+ onSystem?.(`Search provider '${providerId}' selected.
59900
+ `);
59901
+ }
59902
+ function promptSearchFieldStep(otui, r, field3, value) {
59903
+ return new Promise((resolve3) => {
59904
+ const box = overlayBox(otui, r, "search-field-picker");
59905
+ r.root.add(box);
59906
+ box.add(new otui.TextRenderable(r, { id: "sf-title", content: otui.t`${otui.bold(field3.label)} ${otui.dim(`${field3.required ? "required" : "optional"} \xB7 Enter \xB7 Esc to go back`)}` }));
59907
+ const input2 = new otui.InputRenderable(r, { id: "sf-input", value, marginTop: 1 });
59908
+ box.add(input2);
59909
+ input2.focus();
59910
+ const cleanup = () => {
59911
+ unsub();
59912
+ input2.blur();
59913
+ r.root.remove(box);
59914
+ };
59915
+ const unsub = onKeypress4(r, (key) => {
59916
+ if (key.name === "escape") {
59917
+ cleanup();
59918
+ resolve3({ kind: "back" });
59919
+ key.preventDefault();
59920
+ key.stopPropagation();
59921
+ }
59922
+ });
59923
+ input2.on(otui.InputRenderableEvents.ENTER, () => {
59924
+ const entered = input2.value.trim();
59925
+ cleanup();
59926
+ resolve3({ kind: "value", value: entered });
59927
+ });
59928
+ });
59929
+ }
59930
+ function promptSearchCredentialStep(otui, r, opts) {
59931
+ return new Promise((resolve3) => {
59932
+ const box = overlayBox(otui, r, "search-credential-picker");
59933
+ r.root.add(box);
59934
+ box.add(new otui.TextRenderable(r, { id: "sc-title", content: otui.t`${otui.bold(`Paste your ${opts.label}`)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
59935
+ box.add(new otui.TextRenderable(r, {
59936
+ id: "sc-note",
59937
+ content: otui.t`${otui.dim("Saved to your keryx config dir (owner-only, 0600)")}`,
59938
+ marginTop: 1
59939
+ }));
59940
+ const keyInput = new otui.InputRenderable(r, { id: "sc-input", placeholder: "...", marginTop: 1 });
59941
+ box.add(keyInput);
59942
+ keyInput.focus();
59943
+ const cleanup = () => {
59944
+ unsub();
59945
+ keyInput.blur();
59946
+ r.root.remove(box);
59947
+ };
59948
+ const unsub = onKeypress4(r, (key) => {
59949
+ if (key.name === "escape") {
59950
+ cleanup();
59951
+ resolve3({ kind: "back" });
59952
+ key.preventDefault();
59953
+ key.stopPropagation();
59954
+ }
59955
+ });
59956
+ keyInput.on(otui.InputRenderableEvents.ENTER, () => {
59957
+ const value = keyInput.value.trim();
59958
+ cleanup();
59959
+ resolve3(value.length > 0 ? { kind: "key", value } : { kind: "skip" });
59960
+ });
59961
+ });
59962
+ }
59963
+ function promptSetActiveProviderStep(otui, r) {
59964
+ return new Promise((resolve3) => {
59965
+ const box = overlayBox(otui, r, "search-active-picker");
59966
+ r.root.add(box);
59967
+ box.add(new otui.TextRenderable(r, { id: "sa-title", content: otui.t`${otui.bold("Set as active provider after a successful test?")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to go back)")}` }));
59968
+ const select = new otui.SelectRenderable(r, {
59969
+ id: "sa-select",
59970
+ width: 60,
59971
+ height: selectBoxHeight(2, true),
59972
+ options: [
59973
+ { name: "Yes", description: "select it once the test passes" },
59974
+ { name: "No", description: "leave it configured but inactive" }
59975
+ ],
59976
+ selectedTextColor: "#ffd166"
59977
+ });
59978
+ box.add(select);
59979
+ select.focus();
59980
+ const cleanup = () => {
59981
+ unsub();
59982
+ select.blur();
59983
+ r.root.remove(box);
59984
+ };
59985
+ const unsub = onKeypress4(r, (key) => {
59986
+ if (key.name === "escape") {
59987
+ cleanup();
59988
+ resolve3(undefined);
59989
+ key.preventDefault();
59990
+ key.stopPropagation();
59991
+ }
59992
+ });
59993
+ select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
59994
+ const chosen = select.getSelectedOption();
59995
+ cleanup();
59996
+ resolve3(chosen === null ? undefined : chosen.name === "Yes");
59997
+ });
59998
+ });
59999
+ }
60000
+ function pickSearchProviderStep(otui, r, providers) {
60001
+ return new Promise((resolve3) => {
60002
+ const box = overlayBox(otui, r, "search-provider-picker");
60003
+ r.root.add(box);
60004
+ box.add(new otui.TextRenderable(r, { id: "spp-title", content: otui.t`${otui.bold("Select a search provider")} ${otui.dim("(\u2191/\u2193, Enter \xB7 Esc to cancel)")}` }));
60005
+ const labelOf = (p) => `${p.id} (${p.displayName})`;
60006
+ const select = new otui.SelectRenderable(r, {
60007
+ id: "spp-select",
60008
+ width: 60,
60009
+ height: selectBoxHeight(providers.length, true),
60010
+ showScrollIndicator: true,
60011
+ options: providers.map((p) => ({ name: labelOf(p), description: p.kind })),
60012
+ selectedTextColor: "#ffd166"
60013
+ });
60014
+ box.add(select);
60015
+ select.focus();
60016
+ const cleanup = () => {
60017
+ unsub();
60018
+ select.blur();
60019
+ r.root.remove(box);
60020
+ };
60021
+ const unsub = onKeypress4(r, (key) => {
60022
+ if (key.name === "escape") {
60023
+ cleanup();
60024
+ resolve3(undefined);
60025
+ key.preventDefault();
60026
+ key.stopPropagation();
60027
+ }
60028
+ });
60029
+ select.on(otui.SelectRenderableEvents.ITEM_SELECTED, () => {
60030
+ const chosen = select.getSelectedOption();
60031
+ cleanup();
60032
+ resolve3(chosen === null ? undefined : providers.find((p) => labelOf(p) === chosen.name));
60033
+ });
60034
+ });
60035
+ }
60036
+ async function runSearchProviderFieldsStep(otui, r, provider, seed) {
60037
+ const subSteps = [
60038
+ ...provider.fields.map((field3) => ({ kind: "field", field: field3 })),
60039
+ ...provider.credentialSchema.required ? [{ kind: "credential" }] : [],
60040
+ { kind: "toggle" }
60041
+ ];
60042
+ const fields = { ...seed.fields };
60043
+ let credential2 = seed.credential;
60044
+ let setActive = seed.setActive;
60045
+ let index = 0;
60046
+ while (index < subSteps.length) {
60047
+ const step = subSteps[index];
60048
+ if (step === undefined)
60049
+ break;
60050
+ if (step.kind === "field") {
60051
+ const result = await promptSearchFieldStep(otui, r, step.field, fields[step.field.id] ?? step.field.defaultValue ?? "");
60052
+ if (result.kind === "back") {
60053
+ index -= 1;
60054
+ if (index < 0)
60055
+ return { kind: "back" };
60056
+ continue;
60057
+ }
60058
+ fields[step.field.id] = result.value;
60059
+ index += 1;
60060
+ continue;
60061
+ }
60062
+ if (step.kind === "credential") {
60063
+ const result = await promptSearchCredentialStep(otui, r, {
60064
+ label: provider.credentialSchema.label ?? `${provider.displayName} credential`
60065
+ });
60066
+ if (result.kind === "back") {
60067
+ index -= 1;
60068
+ if (index < 0)
60069
+ return { kind: "back" };
60070
+ continue;
60071
+ }
60072
+ credential2 = result.kind === "key" ? result.value : undefined;
60073
+ index += 1;
60074
+ continue;
60075
+ }
60076
+ const toggle = await promptSetActiveProviderStep(otui, r);
60077
+ if (toggle === undefined) {
60078
+ index -= 1;
60079
+ if (index < 0)
60080
+ return { kind: "back" };
60081
+ continue;
60082
+ }
60083
+ setActive = toggle;
60084
+ index += 1;
60085
+ }
60086
+ return { kind: "done", fields, credential: credential2, setActive };
60087
+ }
60088
+ function runSearchProviderTestStep(otui, r, controller, provider, fields, credential2, setActive) {
60089
+ return new Promise((resolve3) => {
60090
+ const box = overlayBox(otui, r, "search-test-picker");
60091
+ r.root.add(box);
60092
+ const status = new otui.TextRenderable(r, { id: "st-title", content: otui.t`${otui.bold(`Testing '${provider.id}'`)} ${otui.dim("...")}` });
60093
+ box.add(status);
60094
+ let settled;
60095
+ const cleanup = () => {
60096
+ unsub();
60097
+ r.root.remove(box);
60098
+ };
60099
+ const unsub = onKeypress4(r, (key) => {
60100
+ if (settled === "failure" && key.name === "escape") {
60101
+ cleanup();
60102
+ resolve3("retry");
60103
+ key.preventDefault();
60104
+ key.stopPropagation();
60105
+ } else if (settled === "success" && (key.name === "return" || key.name === "linefeed" || key.name === "kpenter")) {
60106
+ cleanup();
60107
+ resolve3("done");
60108
+ key.preventDefault();
60109
+ key.stopPropagation();
60110
+ }
60111
+ });
60112
+ (async () => {
60113
+ controller.configure(provider.id, { ...provider.defaults, ...fields }, credential2);
60114
+ const tested = await controller.test(provider.id);
60115
+ if (!tested.ok) {
60116
+ settled = "failure";
60117
+ const reason = tested.reason === "missing-credential" ? "missing credential" : "connection validation failed";
60118
+ status.content = otui.t`${otui.red("\u2717")} ${otui.bold(`'${provider.id}' test failed: ${reason}`)} ${otui.dim("(Esc to go back and retry)")}`;
60119
+ return;
60120
+ }
60121
+ settled = "success";
60122
+ if (!setActive) {
60123
+ status.content = otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured and tested successfully`)} ${otui.dim("(Enter to close)")}`;
60124
+ return;
60125
+ }
60126
+ const selected = await controller.select(provider.id);
60127
+ status.content = selected.ok ? otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured, tested, and set as active`)} ${otui.dim("(Enter to close)")}` : otui.t`${otui.green("\u2713")} ${otui.bold(`'${provider.id}' configured and tested`)} ${otui.dim(`but could not be set active (${selected.reason ?? "unknown"})`)} ${otui.dim("(Enter to close)")}`;
60128
+ })();
60129
+ });
60130
+ }
60131
+ async function searchProviderWizardInTui(otui, r, controller) {
60132
+ const providers = controller.configurable();
60133
+ providerLoop:
60134
+ while (true) {
60135
+ const provider = await pickSearchProviderStep(otui, r, providers);
60136
+ if (provider === undefined) {
60137
+ return;
60138
+ }
60139
+ let seed = { fields: { ...provider.defaults }, credential: undefined, setActive: false };
60140
+ while (true) {
60141
+ const step2 = await runSearchProviderFieldsStep(otui, r, provider, seed);
60142
+ if (step2.kind === "back") {
60143
+ continue providerLoop;
60144
+ }
60145
+ seed = { fields: step2.fields, credential: step2.credential, setActive: step2.setActive };
60146
+ const result = await runSearchProviderTestStep(otui, r, controller, provider, seed.fields, seed.credential, seed.setActive);
60147
+ if (result === "done") {
60148
+ return;
60149
+ }
60150
+ }
60151
+ }
60152
+ }
58559
60153
  function promptBaseUrlStep(otui, r, label, baseUrl2) {
58560
60154
  return new Promise((resolve3) => {
58561
60155
  const box = overlayBox(otui, r, "base-url-picker");
@@ -59332,6 +60926,51 @@ async function launchTuiAgentShell(opts) {
59332
60926
  }));
59333
60927
  return id === "allow";
59334
60928
  }
60929
+ if (tool.startsWith(MCP_ELICITATION_TOOL_PREFIX)) {
60930
+ const described = describeElicitationPrompt(tool, inputJson) ?? { message: inputJson, command: undefined };
60931
+ transcript.add(new otui.TextRenderable(r, {
60932
+ id: `ap${uid++}`,
60933
+ content: otui.t`${otui.yellow("\u2699 codex is requesting approval")}`
60934
+ }));
60935
+ transcript.add(new otui.TextRenderable(r, { id: `ap${uid++}`, content: otui.t`${otui.dim(described.message)}` }));
60936
+ if (described.command !== undefined) {
60937
+ transcript.add(new otui.TextRenderable(r, {
60938
+ id: `ap${uid++}`,
60939
+ content: otui.t`${otui.dim(`command: ${described.command}`)}`
60940
+ }));
60941
+ }
60942
+ if (meta?.destructive === true) {
60943
+ transcript.add(new otui.TextRenderable(r, {
60944
+ id: `ap${uid++}`,
60945
+ content: otui.t`${otui.yellow("deletes a file, touches .git/, or touches many files in one call")}`
60946
+ }));
60947
+ }
60948
+ if (meta?.credentials === true) {
60949
+ transcript.add(new otui.TextRenderable(r, {
60950
+ id: `ap${uid++}`,
60951
+ content: otui.t`${otui.yellow("touches the agent's own permission/credential files")}`
60952
+ }));
60953
+ }
60954
+ chrome.hideMenu();
60955
+ setMainAgent("blocked", "approval");
60956
+ const elicitationSubtitle = described.message.length > 80 ? `${described.message.slice(0, 77)}\u2026` : described.message;
60957
+ const id = await showComposerChoice(otui, r, chrome.dock, {
60958
+ title: "Approve codex elicitation?",
60959
+ subtitle: elicitationSubtitle,
60960
+ cancelId: "deny",
60961
+ options: [
60962
+ { id: "allow", label: "Approve", description: "codex proceeds with this action", recommended: true },
60963
+ { id: "deny", label: "Deny", description: "codex's action is refused" }
60964
+ ]
60965
+ });
60966
+ input2.focus();
60967
+ setMainAgent("running", id === "allow" ? "write" : "denied");
60968
+ transcript.add(new otui.TextRenderable(r, {
60969
+ id: `ap${uid++}`,
60970
+ content: id === "allow" ? otui.t`${otui.green("\u25C7 codex elicitation approved")}` : otui.t`${otui.red("\u25C7 codex elicitation denied")}`
60971
+ }));
60972
+ return id === "allow";
60973
+ }
59335
60974
  const ev = evaluateShellApproval({
59336
60975
  inputJson,
59337
60976
  ...meta !== undefined ? { meta } : {},
@@ -60459,7 +62098,8 @@ Staying in the current session.
60459
62098
  const args2 = parseSearchProviderArgs(line.slice(16));
60460
62099
  const all = searchProviderController.configurable();
60461
62100
  if (args2.providerId === undefined) {
60462
- io.onSystem?.(describeSearchProviderList("Search providers (use /search-provider <id> [key=...]):", all));
62101
+ await chrome.withOverlay(() => searchProviderWizardInTui(otui, r, searchProviderController));
62102
+ input2.focus();
60463
62103
  return;
60464
62104
  }
60465
62105
  const descriptor = all.find((candidate) => candidate.id === args2.providerId);
@@ -60488,11 +62128,18 @@ Staying in the current session.
60488
62128
  const providerId = args2.providerId;
60489
62129
  if (providerId === undefined) {
60490
62130
  const selectable = searchProviderController.selectable();
60491
- io.onSystem?.(describeSearchProviderList("Connected search providers (use /search-connect <id> to select):", selectable));
60492
62131
  if (selectable.length === 0) {
62132
+ io.onSystem?.(describeSearchProviderList("Connected search providers (use /search-connect <id> to select):", selectable));
60493
62133
  io.onSystem?.(`No connected search providers found. Run /search-provider first.
60494
62134
  `);
62135
+ return;
62136
+ }
62137
+ const picked = await chrome.withOverlay(() => pickSearchProviderStep(otui, r, selectable));
62138
+ input2.focus();
62139
+ if (picked === undefined) {
62140
+ return;
60495
62141
  }
62142
+ await selectSearchProviderAndReport(searchProviderController, io.onSystem, picked.id);
60496
62143
  return;
60497
62144
  }
60498
62145
  const normalizedProviderId = searchProviderController.configurable().find((candidate) => candidate.id === providerId)?.id;
@@ -60501,22 +62148,7 @@ Staying in the current session.
60501
62148
  `);
60502
62149
  return;
60503
62150
  }
60504
- const result = await searchProviderController.select(normalizedProviderId);
60505
- if (!result.ok) {
60506
- if (result.reason === "not-configured") {
60507
- io.onSystem?.(`Cannot select '${providerId}': provider is not configured.
60508
- `);
60509
- } else if (result.reason === "not-connected") {
60510
- io.onSystem?.(`Cannot select '${providerId}': provider is not connected (run /search-provider ${providerId} <params> to test).
60511
- `);
60512
- } else {
60513
- io.onSystem?.(`Cannot select '${providerId}': ${result.reason}.
60514
- `);
60515
- }
60516
- return;
60517
- }
60518
- io.onSystem?.(`Search provider '${providerId}' selected.
60519
- `);
62151
+ await selectSearchProviderAndReport(searchProviderController, io.onSystem, normalizedProviderId);
60520
62152
  })();
60521
62153
  return;
60522
62154
  }
@@ -61256,6 +62888,8 @@ init_guard2();
61256
62888
  init_providers();
61257
62889
  var DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434";
61258
62890
  var ANTHROPIC_MODELS = ["claude-sonnet-5", "claude-opus-4-8", "claude-haiku-4-5"];
62891
+ var OPENAI_MODELS = ["gpt-5.6", "gpt-5.6-terra", "gpt-5.6-luna"];
62892
+ var GEMINI_MODELS = ["gemini-3.7-flash", "gemini-2.5-pro", "gemini-2.5-flash-lite"];
61259
62893
  var FAKE_MODELS = ["fake-echo"];
61260
62894
  function isEmbeddingModel(model) {
61261
62895
  const name = typeof model.name === "string" ? model.name.toLowerCase() : "";
@@ -61316,6 +62950,14 @@ async function detectProviders(deps) {
61316
62950
  if (typeof anthropicKey === "string" && anthropicKey.length > 0) {
61317
62951
  detected.push({ name: "anthropic", models: [...ANTHROPIC_MODELS] });
61318
62952
  }
62953
+ const openaiKey = deps.env.OPENAI_API_KEY;
62954
+ if (typeof openaiKey === "string" && openaiKey.length > 0) {
62955
+ detected.push({ name: "openai", models: [...OPENAI_MODELS] });
62956
+ }
62957
+ const geminiKey = deps.env.GEMINI_API_KEY ?? deps.env.GOOGLE_API_KEY;
62958
+ if (typeof geminiKey === "string" && geminiKey.length > 0) {
62959
+ detected.push({ name: "gemini", models: [...GEMINI_MODELS] });
62960
+ }
61319
62961
  for (const p of OPENAI_COMPAT_PROVIDERS) {
61320
62962
  if (!isProviderPlatformSupported(p, platform)) {
61321
62963
  continue;
@@ -62154,6 +63796,37 @@ ${GUTTER}${style.dim("[y/N] ")}`);
62154
63796
  const approved2 = /^y(es)?$/i.test(answer2);
62155
63797
  out(approved2 ? style.green(`approved
62156
63798
  `) : style.red(`denied
63799
+ `));
63800
+ if (!approved2) {
63801
+ return false;
63802
+ }
63803
+ return meta?.fingerprint !== undefined ? { approved: true, fingerprint: meta.fingerprint } : true;
63804
+ }
63805
+ if (tool.startsWith(MCP_ELICITATION_TOOL_PREFIX)) {
63806
+ const described = describeElicitationPrompt(tool, input2) ?? { message: input2, command: undefined };
63807
+ out(`
63808
+ ${GUTTER}${style.yellow("Approve codex elicitation?")}
63809
+ `);
63810
+ out(`${indentBlock(described.message, GUTTER)}
63811
+ `);
63812
+ if (described.command !== undefined) {
63813
+ out(`${GUTTER}${style.dim(`command: ${described.command}`)}
63814
+ `);
63815
+ }
63816
+ if (meta?.destructive === true) {
63817
+ out(`${GUTTER}${style.yellow("deletes a file, touches .git/, or touches many files in one call")}
63818
+ `);
63819
+ }
63820
+ if (meta?.credentials === true) {
63821
+ out(`${GUTTER}${style.yellow("touches the agent's own permission/credential files")}
63822
+ `);
63823
+ }
63824
+ out(`
63825
+ ${GUTTER}${style.dim("[y/N] ")}`);
63826
+ const answer2 = (await readLine() ?? "").trim();
63827
+ const approved2 = /^y(es)?$/i.test(answer2);
63828
+ out(approved2 ? style.green(`approved
63829
+ `) : style.red(`denied
62157
63830
  `));
62158
63831
  if (!approved2) {
62159
63832
  return false;
@@ -65129,7 +66802,7 @@ function printHelp17() {
65129
66802
 
65130
66803
  // src/commands/update.ts
65131
66804
  import { spawn as spawn5 } from "child_process";
65132
- import { chmod as chmod4, mkdir as mkdir55, readFile as readFile79, readdir as readdir25, writeFile as writeFile49 } from "fs/promises";
66805
+ import { chmod as chmod4, mkdir as mkdir55, readFile as readFile79, readdir as readdir26, writeFile as writeFile49 } from "fs/promises";
65133
66806
  import { access as access4, constants as constants2, existsSync as existsSync30 } from "fs";
65134
66807
  import path156 from "path";
65135
66808
  import { fileURLToPath as fileURLToPath7 } from "url";
@@ -65481,7 +67154,7 @@ async function collectTasksDashboardData(metaprojectRoot) {
65481
67154
  }
65482
67155
  let dirEntries;
65483
67156
  try {
65484
- dirEntries = (await readdir25(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
67157
+ dirEntries = (await readdir26(flowsRoot2, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^\d{3}-/.test(entry.name)).map((entry) => entry.name).sort();
65485
67158
  } catch {
65486
67159
  return null;
65487
67160
  }
@@ -65775,7 +67448,7 @@ async function collectMarkdownPages(root, hrefPrefix) {
65775
67448
  return pages;
65776
67449
  }
65777
67450
  async function listMarkdownFiles(root) {
65778
- const entries = await readdir25(root, { withFileTypes: true });
67451
+ const entries = await readdir26(root, { withFileTypes: true });
65779
67452
  const files = [];
65780
67453
  for (const entry of entries) {
65781
67454
  const fullPath = path156.join(root, entry.name);
@@ -66215,7 +67888,7 @@ async function runPostUpdateHooks(projectRoot) {
66215
67888
  if (!await pathExists(hooksDir)) {
66216
67889
  return;
66217
67890
  }
66218
- const entries = (await readdir25(hooksDir)).sort();
67891
+ const entries = (await readdir26(hooksDir)).sort();
66219
67892
  for (const entry of entries) {
66220
67893
  const hookPath = path156.join(hooksDir, entry);
66221
67894
  try {
@@ -67921,7 +69594,7 @@ function oracleRates(score) {
67921
69594
  rates.recall = deriveRate(score.truePositives, score.goldSize, ORACLE_RELIABILITY);
67922
69595
  return Object.keys(rates).length > 0 ? rates : undefined;
67923
69596
  }
67924
- var DEFAULT_MODEL3 = "gdgraph-oracle";
69597
+ var DEFAULT_MODEL5 = "gdgraph-oracle";
67925
69598
  var GOLD_KIND_LABELS = {
67926
69599
  "co-change": "co-change prediction",
67927
69600
  dependency: "graph correctness"
@@ -67960,7 +69633,7 @@ function scoreGoldRun(input2, named, options = {}) {
67960
69633
  variant: "baseline",
67961
69634
  run_id: `${taskId}#1`,
67962
69635
  ladder: options.ladder ?? "metastore",
67963
- model: options.model ?? DEFAULT_MODEL3,
69636
+ model: options.model ?? DEFAULT_MODEL5,
67964
69637
  cacheState: options.cacheState ?? "unknown",
67965
69638
  leakageAssertion: options.leakageAssertion ?? "not-applicable",
67966
69639
  caseKind: "deterministic",
@@ -68204,7 +69877,7 @@ function buildEvidenceBundle(input2, options = {}) {
68204
69877
  run: {
68205
69878
  target: score.target,
68206
69879
  variant,
68207
- model: options.model ?? DEFAULT_MODEL3,
69880
+ model: options.model ?? DEFAULT_MODEL5,
68208
69881
  seed,
68209
69882
  cacheState: options.cacheState ?? "unknown",
68210
69883
  startedAt: timestamp,
@@ -69403,6 +71076,6 @@ if (import.meta.main) {
69403
71076
  });
69404
71077
  }
69405
71078
  export {
69406
- main,
69407
- CLI_ROUTES
71079
+ CLI_ROUTES,
71080
+ main
69408
71081
  };