dsh-github-copilot 0.4.0-alpha.21 → 0.4.0-alpha.23

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/lib/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { C as currentSearchInitiator, D as GITHUB_COPILOT_CREDENTIAL_KEY, E as readCopilotCatalog, O as GITHUB_COPILOT_PREVIEW_PROVIDER_ID, S as currentChatRoute, T as isPluginPreviewProvider, _ as RESPONSES_WEB_SEARCH_TOOL_TYPE, a as COPILOT_HOSTED_SEARCH_PROVIDER_ID, b as candidatesForRoute, c as abortable$1, d as readBounded, f as applyRequestAuth, h as ANTHROPIC_WEB_SEARCH_TOOL_TYPE, i as describeSearchBackend, k as GITHUB_COPILOT_PROVIDER_ID, l as isAbortError, m as providerRequestHeaders, n as routeSessionSearch, o as GITHUB_COPILOT_HOSTED_SEARCH_PROVIDER_ID, p as normalizeRequestAuth, r as DescribedSearchFallbackError, s as createTraditionalSearchProvider, t as isCopilotSearchSelection, u as providerErrorMessage, v as SearchPlan, w as currentSearchSelection, x as sameCandidates, y as WEB_SEARCH_TOOL_TYPE } from "./search-routing-pFLux0W7.js";
2
2
  import AuthorizationService from "@deepseek-ai/dsh-authorization";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
- import { Service } from "@deepseek-ai/cordis";
5
4
  import * as dshScope from "@deepseek-ai/dsh-scope";
6
5
  import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
7
6
  import z from "@deepseek-ai/schemastery";
@@ -18,6 +17,46 @@ import { Config as Config$1, PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
18
17
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "@earendil-works/pi-ai/api/github-copilot-headers";
19
18
  //#region lib/types/dual-model-host.js
20
19
  /** Optional plugin-owned planner/executor sessions; no Core/default-model mutation. */
20
+ var __runInitializers$2 = function(thisArg, initializers, value) {
21
+ var useValue = arguments.length > 2;
22
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
23
+ return useValue ? value : void 0;
24
+ };
25
+ var __esDecorate$2 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
26
+ function accept(f) {
27
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
28
+ return f;
29
+ }
30
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
31
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
32
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
33
+ var _, done = false;
34
+ for (var i = decorators.length - 1; i >= 0; i--) {
35
+ var context = {};
36
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
37
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
38
+ context.addInitializer = function(f) {
39
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
40
+ extraInitializers.push(accept(f || null));
41
+ };
42
+ var result = (0, decorators[i])(kind === "accessor" ? {
43
+ get: descriptor.get,
44
+ set: descriptor.set
45
+ } : descriptor[key], context);
46
+ if (kind === "accessor") {
47
+ if (result === void 0) continue;
48
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
49
+ if (_ = accept(result.get)) descriptor.get = _;
50
+ if (_ = accept(result.set)) descriptor.set = _;
51
+ if (_ = accept(result.init)) initializers.unshift(_);
52
+ } else if (_ = accept(result)) {
53
+ if (kind === "field") initializers.unshift(_);
54
+ else descriptor[key] = _;
55
+ }
56
+ }
57
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
58
+ done = true;
59
+ };
21
60
  const DUAL_MODEL_NAMESPACE = "github-copilot-dual-model";
22
61
  const DUAL_MODEL_POLICY_EVENT = "github-copilot/dual-model-policy";
23
62
  const DUAL_MODEL_PROJECTION = "githubCopilotDualModelPolicy";
@@ -233,525 +272,676 @@ function dedicatedAddress(agent) {
233
272
  const owned = /^copilot-dual-[0-9a-f]{64}$/;
234
273
  return owned.test(agent.id) || agent.session.header.origin === "subagent" && owned.test(agent.session.header.parentSession ?? "");
235
274
  }
236
- /** Always mountable; missing optional APIs produce a safe unsupported view. */
237
- var GitHubCopilotDualModel = class extends Service {
238
- owner;
239
- lifetime = new AbortController();
240
- creates = /* @__PURE__ */ new Map();
241
- overlays = /* @__PURE__ */ new Map();
242
- handles = /* @__PURE__ */ new Map();
243
- settingsReady = false;
244
- projectionReady = false;
245
- registrationFailed = false;
246
- constructor(ctx) {
247
- super(ctx, "githubCopilotDualModel");
248
- this.owner = { ctx };
249
- ctx.inject(["settings"], (scope) => {
250
- const settings = api$1(scope, "settings", [
275
+ let GitHubCopilotDualModel = (() => {
276
+ let _classSuper = TypertRemoteService;
277
+ let _instanceExtraInitializers = [];
278
+ let _view_decorators;
279
+ let _save_decorators;
280
+ let _create_decorators;
281
+ return class GitHubCopilotDualModel extends _classSuper {
282
+ static {
283
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
284
+ _view_decorators = [Remote];
285
+ _save_decorators = [Remote];
286
+ _create_decorators = [Remote];
287
+ __esDecorate$2(this, null, _view_decorators, {
288
+ kind: "method",
289
+ name: "view",
290
+ static: false,
291
+ private: false,
292
+ access: {
293
+ has: (obj) => "view" in obj,
294
+ get: (obj) => obj.view
295
+ },
296
+ metadata: _metadata
297
+ }, null, _instanceExtraInitializers);
298
+ __esDecorate$2(this, null, _save_decorators, {
299
+ kind: "method",
300
+ name: "save",
301
+ static: false,
302
+ private: false,
303
+ access: {
304
+ has: (obj) => "save" in obj,
305
+ get: (obj) => obj.save
306
+ },
307
+ metadata: _metadata
308
+ }, null, _instanceExtraInitializers);
309
+ __esDecorate$2(this, null, _create_decorators, {
310
+ kind: "method",
311
+ name: "create",
312
+ static: false,
313
+ private: false,
314
+ access: {
315
+ has: (obj) => "create" in obj,
316
+ get: (obj) => obj.create
317
+ },
318
+ metadata: _metadata
319
+ }, null, _instanceExtraInitializers);
320
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
321
+ enumerable: true,
322
+ configurable: true,
323
+ writable: true,
324
+ value: _metadata
325
+ });
326
+ }
327
+ owner = __runInitializers$2(this, _instanceExtraInitializers);
328
+ lifetime = new AbortController();
329
+ creates = /* @__PURE__ */ new Map();
330
+ overlays = /* @__PURE__ */ new Map();
331
+ handles = /* @__PURE__ */ new Map();
332
+ settingsReady = false;
333
+ projectionReady = false;
334
+ registrationFailed = false;
335
+ constructor(ctx) {
336
+ super(ctx, "githubCopilotDualModel");
337
+ this.owner = { ctx };
338
+ ctx.inject(["settings"], (scope) => {
339
+ const settings = api$1(scope, "settings", [
340
+ "register",
341
+ "describe",
342
+ "replace"
343
+ ]);
344
+ if (!settings) return;
345
+ try {
346
+ settings.register(DUAL_MODEL_NAMESPACE, configSchema);
347
+ this.settingsReady = true;
348
+ } catch {
349
+ this.registrationFailed = true;
350
+ return;
351
+ }
352
+ scope.effect(() => () => {
353
+ this.settingsReady = false;
354
+ });
355
+ });
356
+ ctx.inject(["sessionProjections"], (scope) => {
357
+ const projections = api$1(scope, "sessionProjections", ["register", "stateOf"]);
358
+ if (!projections) return;
359
+ try {
360
+ projections.register(dualModelProjection);
361
+ this.projectionReady = true;
362
+ } catch {
363
+ this.registrationFailed = true;
364
+ return;
365
+ }
366
+ scope.effect(() => () => {
367
+ this.projectionReady = false;
368
+ });
369
+ });
370
+ ctx.inject(["tools"], (scope) => {
371
+ api$1(scope, "tools", ["guard"])?.guard((exec) => exec.agent && dedicatedAddress(exec.agent) && !this.overlays.has(exec.agent) ? "DUAL_MODEL_UNSUPPORTED" : void 0);
372
+ });
373
+ ctx.on("agent/request", async ({ agent }, next) => {
374
+ const subject = agent;
375
+ if (dedicatedAddress(subject) && !this.overlays.has(subject)) {
376
+ this.restore(subject);
377
+ if (!this.overlays.has(subject)) fail$1("DUAL_MODEL_UNSUPPORTED");
378
+ }
379
+ return next();
380
+ }, { prepend: true });
381
+ ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
382
+ const candidate = context.scope;
383
+ if (!candidate || !object$3(candidate.session) || !object$3(candidate.session.header) || !dedicatedAddress(candidate)) return next();
384
+ const rehydrating = !this.overlays.has(candidate);
385
+ if (rehydrating) this.restore(candidate);
386
+ const installed = this.overlays.get(candidate);
387
+ if (!installed) fail$1("DUAL_MODEL_UNSUPPORTED");
388
+ const result = await next(), { policy, role } = installed;
389
+ const allowed = new Set(role === "planner" ? policy.plannerTools : policy.executorTools);
390
+ const sectionName = "github-copilot:dual-model-role";
391
+ const schemas = rehydrating ? this.requireCapabilities().tools.schemas(candidate) : result.tools;
392
+ return {
393
+ ...result,
394
+ sections: result.sections.some((section) => section.name === sectionName) ? result.sections : [...result.sections, {
395
+ name: sectionName,
396
+ text: roleText(policy, role)
397
+ }],
398
+ tools: schemas.filter((tool) => allowed.has(tool.name)),
399
+ variables: {
400
+ ...result.variables,
401
+ provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
402
+ model: role === "planner" ? policy.plannerModel : policy.executorModel
403
+ }
404
+ };
405
+ }, { prepend: true });
406
+ ctx.on("agent/created", ({ agent }) => this.restore(agent));
407
+ ctx.on("agent/disposed", ({ agent }) => {
408
+ const subject = agent;
409
+ const overlay = this.overlays.get(subject);
410
+ this.overlays.delete(subject);
411
+ if (this.handles.get(agent.id)?.agent === subject) this.handles.delete(agent.id);
412
+ if (overlay) overlay.dispose().catch(() => {});
413
+ });
414
+ ctx.effect(() => async () => {
415
+ this.lifetime.abort();
416
+ await Promise.allSettled([...this.creates.values()].map((value) => value.promise));
417
+ const roots = [...this.overlays].filter(([, value]) => value.role === "planner").map(([agent]) => agent);
418
+ await api$1(ctx, "subagents", ["drainContinuableDescendants"])?.drainContinuableDescendants(roots).catch(() => {});
419
+ for (const [agent] of this.overlays) agent.cancel({ kind: "disposed" });
420
+ await Promise.allSettled([...this.overlays.keys()].map((agent) => agent.whenIdle()));
421
+ await Promise.allSettled([...this.handles.values()].map((handle) => handle.dispose()));
422
+ await Promise.allSettled([...this.overlays.values()].map((overlay) => overlay.dispose()));
423
+ this.handles.clear();
424
+ this.overlays.clear();
425
+ });
426
+ for (const agent of api$1(ctx, "agents", ["list"])?.list() ?? []) try {
427
+ this.restore(agent);
428
+ } catch {}
429
+ }
430
+ capabilities() {
431
+ if (typeof dshScope.createScope !== "function" || this.lifetime.signal.aborted || this.registrationFailed || !this.settingsReady || !this.projectionReady) return void 0;
432
+ const agents = api$1(this.owner.ctx, "agents", [
433
+ "list",
434
+ "get",
435
+ "create",
436
+ "resume"
437
+ ]);
438
+ const workspaces = api$1(this.owner.ctx, "workspaceRegistry", ["get", "list"]);
439
+ const settings = api$1(this.owner.ctx, "settings", [
251
440
  "register",
252
441
  "describe",
253
442
  "replace"
254
443
  ]);
255
- if (!settings) return;
256
- try {
257
- settings.register(DUAL_MODEL_NAMESPACE, configSchema);
258
- this.settingsReady = true;
259
- } catch {
260
- this.registrationFailed = true;
261
- return;
262
- }
263
- scope.effect(() => () => {
264
- this.settingsReady = false;
265
- });
266
- });
267
- ctx.inject(["sessionProjections"], (scope) => {
268
- const projections = api$1(scope, "sessionProjections", ["register", "stateOf"]);
269
- if (!projections) return;
270
- try {
271
- projections.register(dualModelProjection);
272
- this.projectionReady = true;
273
- } catch {
274
- this.registrationFailed = true;
275
- return;
276
- }
277
- scope.effect(() => () => {
278
- this.projectionReady = false;
279
- });
280
- });
281
- ctx.inject(["tools"], (scope) => {
282
- api$1(scope, "tools", ["guard"])?.guard((exec) => exec.agent && dedicatedAddress(exec.agent) && !this.overlays.has(exec.agent) ? "DUAL_MODEL_UNSUPPORTED" : void 0);
283
- });
284
- ctx.on("agent/request", async ({ agent }, next) => {
285
- const subject = agent;
286
- if (dedicatedAddress(subject) && !this.overlays.has(subject)) {
287
- this.restore(subject);
288
- if (!this.overlays.has(subject)) fail$1("DUAL_MODEL_UNSUPPORTED");
289
- }
290
- return next();
291
- }, { prepend: true });
292
- ctx.on("system-prompt/assemble", async (_assembly, context, next) => {
293
- const candidate = context.scope;
294
- if (!candidate || !object$3(candidate.session) || !object$3(candidate.session.header) || !dedicatedAddress(candidate)) return next();
295
- const rehydrating = !this.overlays.has(candidate);
296
- if (rehydrating) this.restore(candidate);
297
- const installed = this.overlays.get(candidate);
298
- if (!installed) fail$1("DUAL_MODEL_UNSUPPORTED");
299
- const result = await next(), { policy, role } = installed;
300
- const allowed = new Set(role === "planner" ? policy.plannerTools : policy.executorTools);
301
- const sectionName = "github-copilot:dual-model-role";
302
- const schemas = rehydrating ? this.requireCapabilities().tools.schemas(candidate) : result.tools;
444
+ const projections = api$1(this.owner.ctx, "sessionProjections", ["register", "stateOf"]);
445
+ const persistence = api$1(this.owner.ctx, "sessionPersistence", ["stat"]);
446
+ const sessions = api$1(this.owner.ctx, "sessions", ["flush"]);
447
+ const inspector = api$1(this.owner.ctx, "sessionController", ["inspect"]);
448
+ const presets = api$1(this.owner.ctx, "agentPresets", [
449
+ "resolve",
450
+ "mount",
451
+ "composedPreset",
452
+ "standingKeyFor"
453
+ ]);
454
+ const llm = api$1(this.owner.ctx, "llm", ["resolveCallConfig"]);
455
+ const tools = api$1(this.owner.ctx, "tools", [
456
+ "register",
457
+ "restrict",
458
+ "guard",
459
+ "schemas",
460
+ "presentAs"
461
+ ]);
462
+ const subagents = api$1(this.owner.ctx, "subagents", [
463
+ "getProvider",
464
+ "startContinuable",
465
+ "sendMessage",
466
+ "listChildren",
467
+ "drainContinuableDescendants"
468
+ ]);
469
+ const preview = api$1(this.owner.ctx, "githubCopilotPreview", ["getView", "discover"]);
470
+ if (!agents || !workspaces || !settings || !projections || !persistence || !sessions || !inspector || !presets || !llm || !tools || !subagents || !preview) return void 0;
471
+ const provider = subagents.getProvider("spawn");
472
+ if (!provider || typeof provider.prepareContinuable !== "function" || !provider.capabilities.agentOptions || !provider.capabilities.persona || !provider.capabilities.toolFilter || !provider.capabilities.depthLimit) return void 0;
303
473
  return {
304
- ...result,
305
- sections: result.sections.some((section) => section.name === sectionName) ? result.sections : [...result.sections, {
306
- name: sectionName,
307
- text: roleText(policy, role)
308
- }],
309
- tools: schemas.filter((tool) => allowed.has(tool.name)),
310
- variables: {
311
- ...result.variables,
312
- provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
313
- model: role === "planner" ? policy.plannerModel : policy.executorModel
314
- }
474
+ agents,
475
+ workspaces,
476
+ settings,
477
+ projections,
478
+ persistence,
479
+ sessions,
480
+ inspector,
481
+ presets,
482
+ llm,
483
+ tools,
484
+ subagents,
485
+ preview
315
486
  };
316
- }, { prepend: true });
317
- ctx.on("agent/created", ({ agent }) => this.restore(agent));
318
- ctx.on("agent/disposed", ({ agent }) => {
319
- const subject = agent;
320
- const overlay = this.overlays.get(subject);
321
- this.overlays.delete(subject);
322
- if (this.handles.get(agent.id)?.agent === subject) this.handles.delete(agent.id);
323
- if (overlay) overlay.dispose().catch(() => {});
324
- });
325
- ctx.effect(() => async () => {
326
- this.lifetime.abort();
327
- await Promise.allSettled([...this.creates.values()].map((value) => value.promise));
328
- const roots = [...this.overlays].filter(([, value]) => value.role === "planner").map(([agent]) => agent);
329
- await api$1(ctx, "subagents", ["drainContinuableDescendants"])?.drainContinuableDescendants(roots).catch(() => {});
330
- for (const [agent] of this.overlays) agent.cancel({ kind: "disposed" });
331
- await Promise.allSettled([...this.overlays.keys()].map((agent) => agent.whenIdle()));
332
- await Promise.allSettled([...this.handles.values()].map((handle) => handle.dispose()));
333
- await Promise.allSettled([...this.overlays.values()].map((overlay) => overlay.dispose()));
334
- this.handles.clear();
335
- this.overlays.clear();
336
- });
337
- for (const agent of api$1(ctx, "agents", ["list"])?.list() ?? []) try {
338
- this.restore(agent);
339
- } catch {}
340
- }
341
- capabilities() {
342
- if (typeof dshScope.createScope !== "function" || this.lifetime.signal.aborted || this.registrationFailed || !this.settingsReady || !this.projectionReady) return void 0;
343
- const agents = api$1(this.owner.ctx, "agents", [
344
- "list",
345
- "get",
346
- "create",
347
- "resume"
348
- ]);
349
- const workspaces = api$1(this.owner.ctx, "workspaceRegistry", ["get", "list"]);
350
- const settings = api$1(this.owner.ctx, "settings", [
351
- "register",
352
- "describe",
353
- "replace"
354
- ]);
355
- const projections = api$1(this.owner.ctx, "sessionProjections", ["register", "stateOf"]);
356
- const persistence = api$1(this.owner.ctx, "sessionPersistence", ["stat"]);
357
- const sessions = api$1(this.owner.ctx, "sessions", ["flush"]);
358
- const inspector = api$1(this.owner.ctx, "sessionController", ["inspect"]);
359
- const presets = api$1(this.owner.ctx, "agentPresets", [
360
- "resolve",
361
- "mount",
362
- "composedPreset",
363
- "standingKeyFor"
364
- ]);
365
- const llm = api$1(this.owner.ctx, "llm", ["resolveCallConfig"]);
366
- const tools = api$1(this.owner.ctx, "tools", [
367
- "register",
368
- "restrict",
369
- "guard",
370
- "schemas",
371
- "presentAs"
372
- ]);
373
- const subagents = api$1(this.owner.ctx, "subagents", [
374
- "getProvider",
375
- "startContinuable",
376
- "sendMessage",
377
- "listChildren",
378
- "drainContinuableDescendants"
379
- ]);
380
- const preview = api$1(this.owner.ctx, "githubCopilotPreview", ["getView", "discover"]);
381
- if (!agents || !workspaces || !settings || !projections || !persistence || !sessions || !inspector || !presets || !llm || !tools || !subagents || !preview) return void 0;
382
- const provider = subagents.getProvider("spawn");
383
- if (!provider || typeof provider.prepareContinuable !== "function" || !provider.capabilities.agentOptions || !provider.capabilities.persona || !provider.capabilities.toolFilter || !provider.capabilities.depthLimit) return void 0;
384
- return {
385
- agents,
386
- workspaces,
387
- settings,
388
- projections,
389
- persistence,
390
- sessions,
391
- inspector,
392
- presets,
393
- llm,
394
- tools,
395
- subagents,
396
- preview
397
- };
398
- }
399
- requireCapabilities() {
400
- return this.capabilities() ?? fail$1("DUAL_MODEL_UNSUPPORTED");
401
- }
402
- configuration() {
403
- const descriptor = api$1(this.owner.ctx, "settings", ["describe"])?.describe({ redactSecrets: true }).find((value) => value.ns === DUAL_MODEL_NAMESPACE);
404
- if (!descriptor) return {
405
- configuration: DEFAULT_CONFIG,
406
- revision: null
407
- };
408
- const parsed = ConfigJson.safeParse(descriptor.value);
409
- if (!parsed.success || !revision(descriptor.revision)) return fail$1("DUAL_MODEL_UNSUPPORTED");
410
- return {
411
- configuration: parsed.data,
412
- revision: descriptor.revision
413
- };
414
- }
415
- async view() {
416
- const empty = {
417
- supported: false,
418
- diagnostic: "DUAL_MODEL_UNSUPPORTED",
419
- writable: false,
420
- revision: null,
421
- configuration: DEFAULT_CONFIG,
422
- models: [],
423
- workspaces: []
424
- };
425
- try {
426
- const configuration = this.configuration(), cap = this.capabilities();
427
- if (!cap) return {
428
- ...empty,
429
- ...configuration
487
+ }
488
+ requireCapabilities() {
489
+ return this.capabilities() ?? fail$1("DUAL_MODEL_UNSUPPORTED");
490
+ }
491
+ configuration() {
492
+ const descriptor = api$1(this.owner.ctx, "settings", ["describe"])?.describe({ redactSecrets: true }).find((value) => value.ns === DUAL_MODEL_NAMESPACE);
493
+ if (!descriptor) return {
494
+ configuration: DEFAULT_CONFIG,
495
+ revision: null
496
+ };
497
+ const parsed = ConfigJson.safeParse(descriptor.value);
498
+ if (!parsed.success || !revision(descriptor.revision)) return fail$1("DUAL_MODEL_UNSUPPORTED");
499
+ return {
500
+ configuration: parsed.data,
501
+ revision: descriptor.revision
502
+ };
503
+ }
504
+ async view() {
505
+ const empty = {
506
+ supported: false,
507
+ diagnostic: "DUAL_MODEL_UNSUPPORTED",
508
+ writable: false,
509
+ revision: null,
510
+ configuration: DEFAULT_CONFIG,
511
+ models: [],
512
+ workspaces: []
430
513
  };
431
- let models = [], diagnostic;
432
514
  try {
433
- const preview = await cap.preview.discover({
434
- force: false,
435
- signal: this.lifetime.signal
436
- });
437
- if (preview.available && preview.state === "ready") models = z$1.array(CatalogEntryJson).max(512).parse(preview.models.map(({ id, name }) => ({
438
- id,
439
- name
440
- })));
441
- else diagnostic = "DUAL_MODEL_MODEL_UNAVAILABLE";
515
+ const configuration = this.configuration(), cap = this.capabilities();
516
+ if (!cap) return {
517
+ ...empty,
518
+ ...configuration
519
+ };
520
+ let models = [], diagnostic;
521
+ try {
522
+ const preview = await cap.preview.discover({
523
+ force: false,
524
+ signal: this.lifetime.signal
525
+ });
526
+ if (preview.available && preview.state === "ready") models = z$1.array(CatalogEntryJson).max(512).parse(preview.models.map(({ id, name }) => ({
527
+ id,
528
+ name
529
+ })));
530
+ else diagnostic = "DUAL_MODEL_MODEL_UNAVAILABLE";
531
+ } catch {
532
+ diagnostic = "DUAL_MODEL_MODEL_UNAVAILABLE";
533
+ }
534
+ return {
535
+ supported: true,
536
+ ...diagnostic === void 0 ? {} : { diagnostic },
537
+ writable: cap.settings.writable === true,
538
+ ...configuration,
539
+ models,
540
+ workspaces: z$1.array(CatalogEntryJson).max(1024).parse(cap.workspaces.list().map((workspace) => ({
541
+ id: workspace.id,
542
+ name: workspace.title
543
+ })))
544
+ };
442
545
  } catch {
443
- diagnostic = "DUAL_MODEL_MODEL_UNAVAILABLE";
546
+ return empty;
444
547
  }
445
- return {
446
- supported: true,
447
- ...diagnostic === void 0 ? {} : { diagnostic },
448
- writable: cap.settings.writable === true,
449
- ...configuration,
450
- models,
451
- workspaces: z$1.array(CatalogEntryJson).max(1024).parse(cap.workspaces.list().map((workspace) => ({
452
- id: workspace.id,
453
- name: workspace.title
454
- })))
455
- };
456
- } catch {
457
- return empty;
458
548
  }
459
- }
460
- async save(input) {
461
- try {
462
- return await this.saveOnce(input);
463
- } catch (error) {
464
- fail$1(reasonOf(error, "DUAL_MODEL_SAVE_FAILED"));
549
+ async save(input) {
550
+ try {
551
+ return await this.saveOnce(input);
552
+ } catch (error) {
553
+ fail$1(reasonOf(error, "DUAL_MODEL_SAVE_FAILED"));
554
+ }
465
555
  }
466
- }
467
- async saveOnce(input) {
468
- const parsed = SaveJson.safeParse(input);
469
- if (!parsed.success) fail$1("DUAL_MODEL_INVALID_REQUEST");
470
- const cap = this.requireCapabilities(), { configuration, expectedRevision } = parsed.data;
471
- if (cap.settings.writable !== true) fail$1("DUAL_MODEL_READ_ONLY");
472
- if (this.configuration().revision !== expectedRevision) fail$1("DUAL_MODEL_REVISION_CONFLICT");
473
- if (configuration.enabled) await this.assertModels(cap, [configuration.plannerModel, configuration.executorModel]);
474
- try {
475
- await cap.settings.replace(DUAL_MODEL_NAMESPACE, configuration, expectedRevision);
476
- } catch (error) {
477
- fail$1(reasonOf(error, "DUAL_MODEL_SAVE_FAILED"));
556
+ async saveOnce(input) {
557
+ const parsed = SaveJson.safeParse(input);
558
+ if (!parsed.success) fail$1("DUAL_MODEL_INVALID_REQUEST");
559
+ const cap = this.requireCapabilities(), { configuration, expectedRevision } = parsed.data;
560
+ if (cap.settings.writable !== true) fail$1("DUAL_MODEL_READ_ONLY");
561
+ if (this.configuration().revision !== expectedRevision) fail$1("DUAL_MODEL_REVISION_CONFLICT");
562
+ if (configuration.enabled) await this.assertModels(cap, [configuration.plannerModel, configuration.executorModel]);
563
+ try {
564
+ await cap.settings.replace(DUAL_MODEL_NAMESPACE, configuration, expectedRevision);
565
+ } catch (error) {
566
+ fail$1(reasonOf(error, "DUAL_MODEL_SAVE_FAILED"));
567
+ }
568
+ return this.view();
478
569
  }
479
- return this.view();
480
- }
481
- create(input) {
482
- const parsed = CreateJson.safeParse(input);
483
- if (!parsed.success) return Promise.reject(new RemoteError("copilot/dual-model", "DUAL_MODEL_INVALID_REQUEST", { reason: "DUAL_MODEL_INVALID_REQUEST" }));
484
- const request = parsed.data, id = dualModelSessionId(request.requestId), key = requestKey(request);
485
- const pending = this.creates.get(id);
486
- if (pending) return pending.key === key ? pending.promise : Promise.reject(new RemoteError("copilot/dual-model", "DUAL_MODEL_REQUEST_CONFLICT", { reason: "DUAL_MODEL_REQUEST_CONFLICT" }));
487
- const promise = this.createOnce(request, id).catch((error) => fail$1(reasonOf(error, "DUAL_MODEL_CREATE_UNCERTAIN"), creationOf(error)));
488
- this.creates.set(id, {
489
- key,
490
- promise
491
- });
492
- promise.then(() => this.creates.delete(id), () => this.creates.delete(id));
493
- return promise;
494
- }
495
- async createOnce(request, id) {
496
- let creation = "uncertain";
497
- try {
498
- const cap = this.requireCapabilities();
499
- if (await cap.persistence.stat(id) !== void 0 || cap.agents.get(id)) {
500
- const inspection = await cap.inspector.inspect(id);
501
- let state = dualModelProjection.init(inspection.meta, inspection.inheritedEventCount);
502
- for (const event of inspection.events) state = dualModelProjection.apply(state, event);
503
- if (state.invalid || !state.policy || !policyMatches(state.policy, request)) fail$1("DUAL_MODEL_REQUEST_CONFLICT");
570
+ create(input) {
571
+ const parsed = CreateJson.safeParse(input);
572
+ if (!parsed.success) return Promise.reject(new RemoteError("copilot/dual-model", "DUAL_MODEL_INVALID_REQUEST", { reason: "DUAL_MODEL_INVALID_REQUEST" }));
573
+ const request = parsed.data, id = dualModelSessionId(request.requestId), key = requestKey(request);
574
+ const pending = this.creates.get(id);
575
+ if (pending) return pending.key === key ? pending.promise : Promise.reject(new RemoteError("copilot/dual-model", "DUAL_MODEL_REQUEST_CONFLICT", { reason: "DUAL_MODEL_REQUEST_CONFLICT" }));
576
+ const promise = this.createOnce(request, id).catch((error) => fail$1(reasonOf(error, "DUAL_MODEL_CREATE_UNCERTAIN"), creationOf(error)));
577
+ this.creates.set(id, {
578
+ key,
579
+ promise
580
+ });
581
+ promise.then(() => this.creates.delete(id), () => this.creates.delete(id));
582
+ return promise;
583
+ }
584
+ async createOnce(request, id) {
585
+ let creation = "uncertain";
586
+ try {
587
+ const cap = this.requireCapabilities();
588
+ if (await cap.persistence.stat(id) !== void 0 || cap.agents.get(id)) {
589
+ const inspection = await cap.inspector.inspect(id);
590
+ let state = dualModelProjection.init(inspection.meta, inspection.inheritedEventCount);
591
+ for (const event of inspection.events) state = dualModelProjection.apply(state, event);
592
+ if (state.invalid || !state.policy || !policyMatches(state.policy, request)) fail$1("DUAL_MODEL_REQUEST_CONFLICT");
593
+ const workspace = this.workspace(cap, request.workspaceId);
594
+ if (workspace.path !== state.policy.cwd) fail$1("DUAL_MODEL_REQUEST_CONFLICT");
595
+ await workspace.attachSession(id);
596
+ return { sessionId: id };
597
+ }
598
+ creation = "not-created";
599
+ const current = this.configuration();
600
+ if (current.revision !== request.expectedRevision) fail$1("DUAL_MODEL_REVISION_CONFLICT");
601
+ if (!current.configuration.enabled) fail$1("DUAL_MODEL_DISABLED");
504
602
  const workspace = this.workspace(cap, request.workspaceId);
505
- if (workspace.path !== state.policy.cwd) fail$1("DUAL_MODEL_REQUEST_CONFLICT");
506
- await workspace.attachSession(id);
507
- return { sessionId: id };
508
- }
509
- creation = "not-created";
510
- const current = this.configuration();
511
- if (current.revision !== request.expectedRevision) fail$1("DUAL_MODEL_REVISION_CONFLICT");
512
- if (!current.configuration.enabled) fail$1("DUAL_MODEL_DISABLED");
513
- const workspace = this.workspace(cap, request.workspaceId);
514
- const captured = { ...current.configuration };
515
- await this.assertModels(cap, [captured.plannerModel, captured.executorModel]);
516
- const preset = await cap.presets.resolve();
517
- const standingKey = await cap.presets.standingKeyFor(preset.id);
518
- const inheritedNames = new Set(cap.tools.schemas(standingKey).map((tool) => tool.name));
519
- const executorTools = EXECUTOR_TOOLS.filter((name) => inheritedNames.has(name));
520
- if (!executorTools.includes("send_message")) fail$1("DUAL_MODEL_UNSUPPORTED");
521
- if (this.configuration().revision !== request.expectedRevision) fail$1("DUAL_MODEL_REVISION_CONFLICT");
522
- if (cap.workspaces.get(workspace.id) !== workspace) fail$1("DUAL_MODEL_WORKSPACE_UNAVAILABLE");
523
- const policy = {
524
- version: 1,
525
- rootSessionId: id,
526
- requestId: request.requestId,
527
- workspaceId: workspace.id,
528
- settingsRevision: request.expectedRevision,
529
- cwd: workspace.path,
530
- agentPreset: preset.id,
531
- provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
532
- plannerModel: captured.plannerModel,
533
- executorModel: captured.executorModel,
534
- plannerTools: [...ROOT_TOOLS],
535
- executorTools: [...executorTools]
536
- };
537
- creation = "uncertain";
538
- const handle = await cap.agents.create({
539
- sessionId: id,
540
- agentOptions: {
541
- provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
542
- model: policy.plannerModel
543
- },
544
- meta: {
545
- cwd: policy.cwd,
603
+ const captured = { ...current.configuration };
604
+ await this.assertModels(cap, [captured.plannerModel, captured.executorModel]);
605
+ const preset = await cap.presets.resolve();
606
+ const standingKey = await cap.presets.standingKeyFor(preset.id);
607
+ const inheritedNames = new Set(cap.tools.schemas(standingKey).map((tool) => tool.name));
608
+ const executorTools = EXECUTOR_TOOLS.filter((name) => inheritedNames.has(name));
609
+ if (!executorTools.includes("send_message")) fail$1("DUAL_MODEL_UNSUPPORTED");
610
+ if (this.configuration().revision !== request.expectedRevision) fail$1("DUAL_MODEL_REVISION_CONFLICT");
611
+ if (cap.workspaces.get(workspace.id) !== workspace) fail$1("DUAL_MODEL_WORKSPACE_UNAVAILABLE");
612
+ const policy = {
613
+ version: 1,
614
+ rootSessionId: id,
615
+ requestId: request.requestId,
616
+ workspaceId: workspace.id,
617
+ settingsRevision: request.expectedRevision,
618
+ cwd: workspace.path,
546
619
  agentPreset: preset.id,
547
- isSeeded: false
548
- },
549
- inheritedEventCount: 0,
550
- seed: [{
551
- type: DUAL_MODEL_POLICY_EVENT,
552
- seq: 0,
553
- time: Date.now(),
554
- ignorable: true,
555
- data: policy
556
- }, {
557
- type: "model/selection",
558
- seq: 1,
559
- time: Date.now(),
560
- data: {
620
+ provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
621
+ plannerModel: captured.plannerModel,
622
+ executorModel: captured.executorModel,
623
+ plannerTools: [...ROOT_TOOLS],
624
+ executorTools: [...executorTools]
625
+ };
626
+ creation = "uncertain";
627
+ const handle = await cap.agents.create({
628
+ sessionId: id,
629
+ agentOptions: {
561
630
  provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
562
631
  model: policy.plannerModel
632
+ },
633
+ meta: {
634
+ cwd: policy.cwd,
635
+ agentPreset: preset.id,
636
+ isSeeded: false
637
+ },
638
+ inheritedEventCount: 0,
639
+ seed: [{
640
+ type: DUAL_MODEL_POLICY_EVENT,
641
+ seq: 0,
642
+ time: Date.now(),
643
+ ignorable: true,
644
+ data: policy
645
+ }, {
646
+ type: "model/selection",
647
+ seq: 1,
648
+ time: Date.now(),
649
+ data: {
650
+ provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
651
+ model: policy.plannerModel
652
+ }
653
+ }],
654
+ signal: this.lifetime.signal,
655
+ setup: async (agentCtx, agent) => {
656
+ await cap.presets.mount(agentCtx, preset.id);
657
+ this.install(agent, policy, "planner");
563
658
  }
564
- }],
565
- signal: this.lifetime.signal,
566
- setup: async (agentCtx, agent) => {
567
- await cap.presets.mount(agentCtx, preset.id);
568
- this.install(agent, policy, "planner");
569
- }
570
- });
571
- this.handles.set(id, handle);
572
- if (!await cap.sessions.flush(handle.agent.session)) fail$1("DUAL_MODEL_CREATE_UNCERTAIN");
573
- await workspace.attachSession(id);
574
- return { sessionId: id };
575
- } catch (error) {
576
- fail$1(reasonOf(error, "DUAL_MODEL_CREATE_UNCERTAIN"), creation);
577
- }
578
- }
579
- workspace(cap, id) {
580
- const workspace = cap.workspaces.get(id);
581
- if (!workspace || typeof workspace.path !== "string" || typeof workspace.attachSession !== "function") fail$1("DUAL_MODEL_WORKSPACE_UNAVAILABLE");
582
- return workspace;
583
- }
584
- async assertModels(cap, models, signal = this.lifetime.signal) {
585
- try {
586
- signal.throwIfAborted();
587
- const preview = await cap.preview.discover({
588
- force: false,
589
- signal
590
- });
591
- if (preview.provider !== "github-copilot-preview" || !preview.available || preview.state !== "ready") fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
592
- for (const model of models) {
593
- if (!model || !preview.models.some((item) => item.id === model)) fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
594
- const resolved = await cap.llm.resolveCallConfig({
595
- provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
596
- model
597
- }, signal);
598
- if (resolved.provider !== "github-copilot-preview" || resolved.model !== model) fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
659
+ });
660
+ this.handles.set(id, handle);
661
+ if (!await cap.sessions.flush(handle.agent.session)) fail$1("DUAL_MODEL_CREATE_UNCERTAIN");
662
+ await workspace.attachSession(id);
663
+ return { sessionId: id };
664
+ } catch (error) {
665
+ fail$1(reasonOf(error, "DUAL_MODEL_CREATE_UNCERTAIN"), creation);
599
666
  }
600
- const now = cap.preview.getView();
601
- if (!now.available || now.state !== "ready" || models.some((model) => !now.models.some((item) => item.id === model))) fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
602
- signal.throwIfAborted();
603
- } catch {
604
- fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
605
- }
606
- }
607
- projection(agent) {
608
- const projections = api$1(this.owner.ctx, "sessionProjections", ["stateOf"]);
609
- const parsed = ProjectionJson.safeParse(projections?.stateOf(agent.session, DUAL_MODEL_PROJECTION));
610
- return parsed.success ? parsed.data : void 0;
611
- }
612
- restore(agent) {
613
- if (this.overlays.has(agent)) return;
614
- const state = this.projection(agent);
615
- if (!state) {
616
- if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_UNSUPPORTED");
617
- return;
618
667
  }
619
- if (state.invalid) fail$1("DUAL_MODEL_POLICY_INVALID");
620
- if (state.policy) {
621
- this.install(agent, state.policy, "planner");
622
- return;
668
+ workspace(cap, id) {
669
+ const workspace = cap.workspaces.get(id);
670
+ if (!workspace || typeof workspace.path !== "string" || typeof workspace.attachSession !== "function") fail$1("DUAL_MODEL_WORKSPACE_UNAVAILABLE");
671
+ return workspace;
623
672
  }
624
- if (state.origin !== "subagent" || state.parentId === null) {
625
- if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_POLICY_INVALID");
626
- return;
673
+ async assertModels(cap, models, signal = this.lifetime.signal) {
674
+ try {
675
+ signal.throwIfAborted();
676
+ const preview = await cap.preview.discover({
677
+ force: false,
678
+ signal
679
+ });
680
+ if (preview.provider !== "github-copilot-preview" || !preview.available || preview.state !== "ready") fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
681
+ for (const model of models) {
682
+ if (!model || !preview.models.some((item) => item.id === model)) fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
683
+ const resolved = await cap.llm.resolveCallConfig({
684
+ provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
685
+ model
686
+ }, signal);
687
+ if (resolved.provider !== "github-copilot-preview" || resolved.model !== model) fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
688
+ }
689
+ const now = cap.preview.getView();
690
+ if (!now.available || now.state !== "ready" || models.some((model) => !now.models.some((item) => item.id === model))) fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
691
+ signal.throwIfAborted();
692
+ } catch {
693
+ fail$1("DUAL_MODEL_MODEL_UNAVAILABLE");
694
+ }
627
695
  }
628
- const parent = api$1(this.owner.ctx, "agents", ["get"])?.get(state.parentId);
629
- if (!parent) {
630
- if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_UNSUPPORTED");
631
- return;
696
+ projection(agent) {
697
+ const projections = api$1(this.owner.ctx, "sessionProjections", ["stateOf"]);
698
+ const parsed = ProjectionJson.safeParse(projections?.stateOf(agent.session, DUAL_MODEL_PROJECTION));
699
+ return parsed.success ? parsed.data : void 0;
632
700
  }
633
- if (this.overlays.get(parent)?.role === "executor") fail$1("DUAL_MODEL_DELEGATION_DENIED");
634
- const parentPolicy = this.projection(parent)?.policy;
635
- if (!parentPolicy) {
636
- if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_POLICY_INVALID");
637
- return;
701
+ restore(agent) {
702
+ if (this.overlays.has(agent)) return;
703
+ const state = this.projection(agent);
704
+ if (!state) {
705
+ if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_UNSUPPORTED");
706
+ return;
707
+ }
708
+ if (state.invalid) fail$1("DUAL_MODEL_POLICY_INVALID");
709
+ if (state.policy) {
710
+ this.install(agent, state.policy, "planner");
711
+ return;
712
+ }
713
+ if (state.origin !== "subagent" || state.parentId === null) {
714
+ if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_POLICY_INVALID");
715
+ return;
716
+ }
717
+ const parent = api$1(this.owner.ctx, "agents", ["get"])?.get(state.parentId);
718
+ if (!parent) {
719
+ if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_UNSUPPORTED");
720
+ return;
721
+ }
722
+ if (this.overlays.get(parent)?.role === "executor") fail$1("DUAL_MODEL_DELEGATION_DENIED");
723
+ const parentPolicy = this.projection(parent)?.policy;
724
+ if (!parentPolicy) {
725
+ if (dedicatedAddress(agent)) fail$1("DUAL_MODEL_POLICY_INVALID");
726
+ return;
727
+ }
728
+ if (!state.child || state.child.agentProvider !== "github-copilot-preview" || state.child.agentModel !== parentPolicy.executorModel) fail$1("DUAL_MODEL_POLICY_INVALID");
729
+ this.install(agent, parentPolicy, "executor");
638
730
  }
639
- if (!state.child || state.child.agentProvider !== "github-copilot-preview" || state.child.agentModel !== parentPolicy.executorModel) fail$1("DUAL_MODEL_POLICY_INVALID");
640
- this.install(agent, parentPolicy, "executor");
641
- }
642
- install(agent, policy, role) {
643
- if (this.overlays.has(agent)) return;
644
- this.requireCapabilities();
645
- if (typeof agent.cancel !== "function" || typeof agent.whenIdle !== "function") fail$1("DUAL_MODEL_UNSUPPORTED");
646
- const overlay = dshScope.createScope(agent.ctx, agent), scoped = overlay.ctx;
647
- try {
648
- const tools = api$1(scoped, "tools", [
649
- "schemas",
650
- "register",
651
- "restrict",
652
- "guard",
653
- "presentAs"
654
- ]);
655
- const prompt = api$1(scoped, "systemPrompt", ["section"]);
656
- if (!tools || !prompt) fail$1("DUAL_MODEL_UNSUPPORTED");
657
- const allowed = new Set(role === "planner" ? policy.plannerTools : policy.executorTools);
658
- const available = tools.schemas(agent).map((item) => item.name);
659
- tools.guard((exec) => allowed.has(exec.name) ? void 0 : "DUAL_MODEL_TOOL_DENIED");
660
- tools.presentAs("native");
661
- tools.restrict({ allow: available.filter((name) => allowed.has(name) && name !== "copilot_execute") });
662
- const selectedModel = role === "planner" ? policy.plannerModel : policy.executorModel;
663
- prompt.section({
664
- name: "github-copilot:dual-model-role",
665
- order: 1e3,
666
- text: roleText(policy, role)
667
- });
668
- scoped.on("agent/request", async ({ signal }, next) => {
669
- await this.assertModels(this.requireCapabilities(), [selectedModel], signal);
670
- const resolved = await next();
671
- if (resolved.provider !== "github-copilot-preview" || resolved.model !== selectedModel) fail$1("DUAL_MODEL_SELECTION_LOCKED");
672
- return resolved;
673
- }, { prepend: true });
674
- if (role === "planner") tools.register({
675
- name: DUAL_MODEL_EXECUTE_TOOL,
676
- description: `Delegate implementation to a continuable executor fixed to ${GITHUB_COPILOT_PREVIEW_PROVIDER_ID}/${policy.executorModel}. Returns a durable child ID, not completion. Continue with send_message and inspect with list_agents. No model override or recursive delegation is allowed.`,
677
- parameters: {
678
- type: "object",
679
- additionalProperties: false,
680
- properties: {
681
- description: { type: "string" },
682
- prompt: { type: "string" }
683
- },
684
- required: ["description", "prompt"]
685
- },
686
- output: {
687
- schema: {
731
+ install(agent, policy, role) {
732
+ if (this.overlays.has(agent)) return;
733
+ this.requireCapabilities();
734
+ if (typeof agent.cancel !== "function" || typeof agent.whenIdle !== "function") fail$1("DUAL_MODEL_UNSUPPORTED");
735
+ const overlay = dshScope.createScope(agent.ctx, agent), scoped = overlay.ctx;
736
+ try {
737
+ const tools = api$1(scoped, "tools", [
738
+ "schemas",
739
+ "register",
740
+ "restrict",
741
+ "guard",
742
+ "presentAs"
743
+ ]);
744
+ const prompt = api$1(scoped, "systemPrompt", ["section"]);
745
+ if (!tools || !prompt) fail$1("DUAL_MODEL_UNSUPPORTED");
746
+ const allowed = new Set(role === "planner" ? policy.plannerTools : policy.executorTools);
747
+ const available = tools.schemas(agent).map((item) => item.name);
748
+ tools.guard((exec) => allowed.has(exec.name) ? void 0 : "DUAL_MODEL_TOOL_DENIED");
749
+ tools.presentAs("native");
750
+ tools.restrict({ allow: available.filter((name) => allowed.has(name) && name !== "copilot_execute") });
751
+ const selectedModel = role === "planner" ? policy.plannerModel : policy.executorModel;
752
+ prompt.section({
753
+ name: "github-copilot:dual-model-role",
754
+ order: 1e3,
755
+ text: roleText(policy, role)
756
+ });
757
+ scoped.on("agent/request", async ({ signal }, next) => {
758
+ await this.assertModels(this.requireCapabilities(), [selectedModel], signal);
759
+ const resolved = await next();
760
+ if (resolved.provider !== "github-copilot-preview" || resolved.model !== selectedModel) fail$1("DUAL_MODEL_SELECTION_LOCKED");
761
+ return resolved;
762
+ }, { prepend: true });
763
+ if (role === "planner") tools.register({
764
+ name: DUAL_MODEL_EXECUTE_TOOL,
765
+ description: `Delegate implementation to a continuable executor fixed to ${GITHUB_COPILOT_PREVIEW_PROVIDER_ID}/${policy.executorModel}. Returns a durable child ID, not completion. Continue with send_message and inspect with list_agents. No model override or recursive delegation is allowed.`,
766
+ parameters: {
688
767
  type: "object",
689
768
  additionalProperties: false,
690
769
  properties: {
691
- childId: { type: "string" },
692
- provider: { type: "string" },
693
- model: { type: "string" }
770
+ description: { type: "string" },
771
+ prompt: { type: "string" }
694
772
  },
695
- required: [
696
- "childId",
697
- "provider",
698
- "model"
699
- ]
773
+ required: ["description", "prompt"]
700
774
  },
701
- render: (_args, value) => [{
702
- type: "text",
703
- text: JSON.stringify(value)
704
- }]
705
- },
706
- execute: async (input, exec) => {
707
- try {
708
- const parsed = ExecuteJson.safeParse(input);
709
- if (!parsed.success || exec.agent !== agent) fail$1("DUAL_MODEL_INVALID_REQUEST");
710
- const current = this.requireCapabilities();
711
- await this.assertModels(current, [policy.executorModel], exec.signal);
712
- return {
713
- childId: (await current.subagents.startContinuable({
714
- provider: "spawn",
715
- label: parsed.data.description,
716
- request: {
717
- parent: agent,
718
- prompt: [{
719
- type: "text",
720
- text: parsed.data.prompt
721
- }],
722
- agentOptions: {
723
- provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
724
- model: policy.executorModel
775
+ output: {
776
+ schema: {
777
+ type: "object",
778
+ additionalProperties: false,
779
+ properties: {
780
+ childId: { type: "string" },
781
+ provider: { type: "string" },
782
+ model: { type: "string" }
783
+ },
784
+ required: [
785
+ "childId",
786
+ "provider",
787
+ "model"
788
+ ]
789
+ },
790
+ render: (_args, value) => [{
791
+ type: "text",
792
+ text: JSON.stringify(value)
793
+ }]
794
+ },
795
+ execute: async (input, exec) => {
796
+ try {
797
+ const parsed = ExecuteJson.safeParse(input);
798
+ if (!parsed.success || exec.agent !== agent) fail$1("DUAL_MODEL_INVALID_REQUEST");
799
+ const current = this.requireCapabilities();
800
+ await this.assertModels(current, [policy.executorModel], exec.signal);
801
+ return {
802
+ childId: (await current.subagents.startContinuable({
803
+ provider: "spawn",
804
+ label: parsed.data.description,
805
+ request: {
806
+ parent: agent,
807
+ prompt: [{
808
+ type: "text",
809
+ text: parsed.data.prompt
810
+ }],
811
+ agentOptions: {
812
+ provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
813
+ model: policy.executorModel
814
+ },
815
+ persona: EXECUTOR_PERSONA,
816
+ toolFilter: { allow: policy.executorTools },
817
+ maxDepth: 1
725
818
  },
726
- persona: EXECUTOR_PERSONA,
727
- toolFilter: { allow: policy.executorTools },
728
- maxDepth: 1
729
- },
730
- signal: exec.signal
731
- })).childId,
732
- provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
733
- model: policy.executorModel
734
- };
735
- } catch (error) {
736
- fail$1(reasonOf(error, "DUAL_MODEL_EXECUTION_FAILED"));
819
+ signal: exec.signal
820
+ })).childId,
821
+ provider: GITHUB_COPILOT_PREVIEW_PROVIDER_ID,
822
+ model: policy.executorModel
823
+ };
824
+ } catch (error) {
825
+ fail$1(reasonOf(error, "DUAL_MODEL_EXECUTION_FAILED"));
826
+ }
737
827
  }
738
- }
739
- });
740
- this.overlays.set(agent, {
741
- dispose: overlay.dispose,
742
- policy,
743
- role
744
- });
745
- } catch (error) {
746
- overlay.dispose().catch(() => {});
747
- throw error;
828
+ });
829
+ this.overlays.set(agent, {
830
+ dispose: overlay.dispose,
831
+ policy,
832
+ role
833
+ });
834
+ } catch (error) {
835
+ overlay.dispose().catch(() => {});
836
+ throw error;
837
+ }
748
838
  }
839
+ };
840
+ })();
841
+ //#endregion
842
+ //#region lib/types/search-routing-remote.js
843
+ const SearchProviderCatalogSchema = z$1.object({
844
+ supported: z$1.boolean(),
845
+ providers: z$1.array(z$1.object({ id: z$1.string().min(1).max(512) }).strict()).max(512)
846
+ }).strict();
847
+ //#endregion
848
+ //#region lib/types/search-routing-host.js
849
+ var __runInitializers$1 = function(thisArg, initializers, value) {
850
+ var useValue = arguments.length > 2;
851
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
852
+ return useValue ? value : void 0;
853
+ };
854
+ var __esDecorate$1 = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
855
+ function accept(f) {
856
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
857
+ return f;
749
858
  }
859
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
860
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
861
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
862
+ var _, done = false;
863
+ for (var i = decorators.length - 1; i >= 0; i--) {
864
+ var context = {};
865
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
866
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
867
+ context.addInitializer = function(f) {
868
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
869
+ extraInitializers.push(accept(f || null));
870
+ };
871
+ var result = (0, decorators[i])(kind === "accessor" ? {
872
+ get: descriptor.get,
873
+ set: descriptor.set
874
+ } : descriptor[key], context);
875
+ if (kind === "accessor") {
876
+ if (result === void 0) continue;
877
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
878
+ if (_ = accept(result.get)) descriptor.get = _;
879
+ if (_ = accept(result.set)) descriptor.set = _;
880
+ if (_ = accept(result.init)) initializers.unshift(_);
881
+ } else if (_ = accept(result)) {
882
+ if (kind === "field") initializers.unshift(_);
883
+ else descriptor[key] = _;
884
+ }
885
+ }
886
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
887
+ done = true;
750
888
  };
889
+ let SearchRoutingController = (() => {
890
+ let _classSuper = TypertRemoteService;
891
+ let _instanceExtraInitializers = [];
892
+ let _providers_decorators;
893
+ return class SearchRoutingController extends _classSuper {
894
+ static {
895
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
896
+ _providers_decorators = [Remote];
897
+ __esDecorate$1(this, null, _providers_decorators, {
898
+ kind: "method",
899
+ name: "providers",
900
+ static: false,
901
+ private: false,
902
+ access: {
903
+ has: (obj) => "providers" in obj,
904
+ get: (obj) => obj.providers
905
+ },
906
+ metadata: _metadata
907
+ }, null, _instanceExtraInitializers);
908
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
909
+ enumerable: true,
910
+ configurable: true,
911
+ writable: true,
912
+ value: _metadata
913
+ });
914
+ }
915
+ constructor(ctx) {
916
+ super(ctx, "githubCopilotSearchRouting");
917
+ __runInitializers$1(this, _instanceExtraInitializers);
918
+ }
919
+ providers() {
920
+ const unavailable = {
921
+ supported: false,
922
+ providers: []
923
+ };
924
+ const catalog = this.ctx.get("githubCopilotSearchCatalog");
925
+ if (typeof catalog?.list !== "function") return unavailable;
926
+ try {
927
+ const ids = catalog.list();
928
+ const parsed = SearchProviderCatalogSchema.safeParse({
929
+ supported: true,
930
+ providers: ids.map((id) => ({ id }))
931
+ });
932
+ if (!parsed.success || new Set(ids).size !== ids.length) return unavailable;
933
+ parsed.data.providers.sort((a, b) => a.id.localeCompare(b.id));
934
+ return parsed.data;
935
+ } catch {
936
+ return unavailable;
937
+ }
938
+ }
939
+ };
940
+ })();
751
941
  //#endregion
752
942
  //#region package.json
753
943
  var name$1 = "dsh-github-copilot";
754
- var version = "0.4.0-alpha.21";
944
+ var version = "0.4.0-alpha.23";
755
945
  //#endregion
756
946
  //#region lib/types/probe.js
757
947
  /**
@@ -1079,6 +1269,18 @@ const Config = z.object({
1079
1269
  temporaryRouteBackup: z.string().hidden()
1080
1270
  });
1081
1271
  //#endregion
1272
+ //#region lib/types/search-routing-policy.js
1273
+ /** Resolve old and new settings without guessing registered providers or dropping unknown ids. */
1274
+ function normalizeWebSearchRouting(config) {
1275
+ const defaultProvider = config.defaultSearchProvider?.trim() || "deepseek-official";
1276
+ const legacy = config.searchProvider === void 0;
1277
+ return {
1278
+ primaryProvider: legacy ? config.searchMode === "fixed" ? defaultProvider : "auto" : config.searchProvider?.trim() || "auto",
1279
+ defaultProvider,
1280
+ legacy
1281
+ };
1282
+ }
1283
+ //#endregion
1082
1284
  //#region lib/types/web-search-routing-config.js
1083
1285
  /** Plugin-owned settings namespace for cross-provider web-search routing. */
1084
1286
  const WEB_SEARCH_ROUTING_SETTINGS_NAMESPACE = "github-copilot-search-routing";
@@ -1086,10 +1288,90 @@ const WEB_SEARCH_ROUTING_SETTINGS_NAMESPACE = "github-copilot-search-routing";
1086
1288
  const NO_DEFAULT_SEARCH_PROVIDER = "none";
1087
1289
  /** User-facing routing settings; provider-specific credentials and models remain provider-owned. */
1088
1290
  const WebSearchRoutingConfigSchema = z.object({
1291
+ searchProvider: z.string(),
1089
1292
  searchMode: z.union(["auto", "fixed"]).default("auto"),
1090
1293
  defaultSearchProvider: z.string().default("deepseek-official")
1091
1294
  });
1092
1295
  //#endregion
1296
+ //#region lib/types/search-tool-routing.js
1297
+ function aborted() {
1298
+ return new WebError("web search aborted", "WEB_ABORTED");
1299
+ }
1300
+ function isAborted(signal) {
1301
+ return signal?.aborted === true;
1302
+ }
1303
+ function assertContinuable(signal, canContinue) {
1304
+ if (isAborted(signal)) throw aborted();
1305
+ let current = false;
1306
+ try {
1307
+ current = canContinue();
1308
+ } catch {}
1309
+ if (isAborted(signal)) throw aborted();
1310
+ if (!current) throw new WebError("web search owner or account proof invalidated", "WEB_PROVIDER_UNAVAILABLE");
1311
+ }
1312
+ function isCancellation(error) {
1313
+ return error instanceof WebError && error.code === "WEB_ABORTED" || error instanceof Error && error.name === "AbortError";
1314
+ }
1315
+ /** Only shared public codes may leave the provider boundary; messages, causes and custom codes stay private. */
1316
+ function failureCode(error) {
1317
+ if (error instanceof WebError && [
1318
+ "WEB_PROVIDER_CONFIGURED_MISSING",
1319
+ "WEB_PROVIDER_CONFIGURED_UNAVAILABLE",
1320
+ "WEB_PROVIDER_UNAVAILABLE",
1321
+ "WEB_PROVIDER_ERROR",
1322
+ "WEB_PROVIDER_AMBIGUOUS"
1323
+ ].includes(error.code)) return error.code;
1324
+ return "WEB_PROVIDER_ERROR";
1325
+ }
1326
+ /**
1327
+ * Execute a captured primary and optional final fallback without registry or credential access.
1328
+ * The caller resolves Auto, handles disabled policy, proves registrations and supplies lifetime guards.
1329
+ * Generic providers must honor the signal; this helper cannot inspect their internal auth/HTTP boundary.
1330
+ */
1331
+ async function routeSearchTools(request, signal, deps) {
1332
+ const { primary, fallback, canContinue, allowFailureFallback = true } = deps;
1333
+ const primaryId = primary?.id;
1334
+ const primarySearch = primary?.search.bind(primary);
1335
+ const fallbackId = fallback?.id;
1336
+ const fallbackSearch = fallback?.search.bind(fallback);
1337
+ assertContinuable(signal, canContinue);
1338
+ let primaryFailure;
1339
+ if (primarySearch !== void 0) {
1340
+ let result;
1341
+ try {
1342
+ result = await primarySearch(request, signal);
1343
+ } catch (error) {
1344
+ assertContinuable(signal, canContinue);
1345
+ if (isCancellation(error)) throw aborted();
1346
+ primaryFailure = error;
1347
+ if (!allowFailureFallback || fallbackSearch === void 0 || fallbackId === primaryId) throw new WebError(`Search provider ${JSON.stringify(primaryId)} failed (${failureCode(error)}).`, failureCode(error));
1348
+ return runFallback();
1349
+ }
1350
+ assertContinuable(signal, canContinue);
1351
+ return result;
1352
+ }
1353
+ return runFallback();
1354
+ async function runFallback() {
1355
+ assertContinuable(signal, canContinue);
1356
+ if (fallbackSearch === void 0) throw new WebError("no selected search provider is available", "WEB_PROVIDER_UNAVAILABLE");
1357
+ const reason = primarySearch === void 0 ? "no matching primary provider" : failureCode(primaryFailure);
1358
+ const notice = `Search provider: ${JSON.stringify(fallbackId)} (final fallback${primaryId === void 0 ? "" : ` from ${JSON.stringify(primaryId)}`}; reason: ${reason}). This fallback may incur charges from the selected search backend.`;
1359
+ let result;
1360
+ try {
1361
+ result = await fallbackSearch(request, signal);
1362
+ } catch (error) {
1363
+ assertContinuable(signal, canContinue);
1364
+ if (isCancellation(error)) throw aborted();
1365
+ throw new WebError(`${notice} The final fallback failed (${failureCode(error)}).`, "WEB_PROVIDER_ERROR");
1366
+ }
1367
+ assertContinuable(signal, canContinue);
1368
+ return {
1369
+ ...result,
1370
+ content: `${notice}\n\n${result.content ?? ""}`.trimEnd()
1371
+ };
1372
+ }
1373
+ }
1374
+ //#endregion
1093
1375
  //#region lib/types/sse.js
1094
1376
  /**
1095
1377
  * Bounded SSE line parser for the inline wire path. Handles `event:` and
@@ -5662,6 +5944,11 @@ const integrationInject = [
5662
5944
  ];
5663
5945
  /** Settings namespace carrying this plugin's section. */
5664
5946
  const GITHUB_COPILOT_SETTINGS_NAMESPACE = "github-copilot";
5947
+ var ConfiguredSearchProofInvalidated = class extends WebError {
5948
+ constructor() {
5949
+ super("configured Copilot search owner or account proof invalidated", "WEB_PROVIDER_UNAVAILABLE");
5950
+ }
5951
+ };
5665
5952
  function isInstanceSettingsInstaller(value) {
5666
5953
  return typeof value === "object" && value !== null && "installSection" in value && typeof value.installSection === "function";
5667
5954
  }
@@ -5728,6 +6015,7 @@ function activate(ctx, config) {
5728
6015
  ctx.plugin(preview_route_default, { accountModelSettings: () => current() });
5729
6016
  ctx.plugin(GitHubCopilotAuthorizationController);
5730
6017
  ctx.plugin(GitHubCopilotDualModel);
6018
+ ctx.plugin(SearchRoutingController);
5731
6019
  const resolveGitHubCopilotToken = createGitHubCopilotTokenResolver(ctx, async () => {
5732
6020
  await ensureGitHubCopilotProviderProfile(ctx);
5733
6021
  });
@@ -5927,37 +6215,110 @@ function activate(ctx, config) {
5927
6215
  const owner = currentSearchInitiator(ctx);
5928
6216
  const startedGeneration = generation;
5929
6217
  const cfg = current();
5930
- let proof;
5931
- let preview;
6218
+ const preview = ctx.get("githubCopilotPreview");
6219
+ const proof = preview?.captureSearchProof();
5932
6220
  const assertCurrent = () => {
5933
6221
  if (signal?.aborted) throw new WebError("web search aborted", "WEB_ABORTED");
5934
- if (!active || generation !== startedGeneration || owner === void 0 || disposedOwners.has(owner) || preview === void 0 || ctx.get("githubCopilotPreview") !== preview || proof?.() !== true) throw new WebError("configured Copilot search owner or account proof invalidated", "WEB_PROVIDER_UNAVAILABLE");
6222
+ if (!active || generation !== startedGeneration || owner === void 0 || disposedOwners.has(owner) || preview === void 0 || ctx.get("githubCopilotPreview") !== preview || proof?.() !== true) throw new ConfiguredSearchProofInvalidated();
5935
6223
  };
5936
- const result = await createTraditionalSearchProvider(configuredAvailable, async (requestSignal) => {
6224
+ assertCurrent();
6225
+ const provider = createTraditionalSearchProvider(configuredAvailable, async (requestSignal) => {
6226
+ assertCurrent();
5937
6227
  const searchPlan = await configuredWebPlan(requestSignal);
5938
- preview = ctx.get("githubCopilotPreview");
5939
- proof = preview?.captureSearchProof();
5940
6228
  assertCurrent();
5941
6229
  return searchPlan;
5942
6230
  }, {
5943
6231
  ...hooks,
5944
6232
  resolveApiKey: async (candidate) => {
6233
+ assertCurrent();
5945
6234
  const auth = await hooks.resolveApiKey(candidate);
5946
- if (proof !== void 0) assertCurrent();
6235
+ assertCurrent();
5947
6236
  return auth;
5948
6237
  }
5949
- }, () => cfg, "github-copilot-hosted requires github-copilot.searchModel selecting an account-authorized OpenAI Responses model").search(request, signal);
5950
- assertCurrent();
5951
- return result;
6238
+ }, () => cfg, "github-copilot-hosted requires github-copilot.searchModel selecting an account-authorized OpenAI Responses model");
6239
+ try {
6240
+ const result = await provider.search(request, signal);
6241
+ assertCurrent();
6242
+ return result;
6243
+ } catch (error) {
6244
+ assertCurrent();
6245
+ throw error;
6246
+ }
5952
6247
  }
5953
6248
  ctx.web.registerSearchProvider(traditionalProvider);
5954
- ctx.provide("githubCopilotSearchRouter", { search: async (request, signal, delegate, selectProvider) => {
6249
+ ctx.provide("githubCopilotSearchRouter", { search: async (request, signal, delegate, selectProvider, captureSearchProvider) => {
5955
6250
  const cfg = current();
5956
6251
  const routing = routingCurrent();
6252
+ const policy = normalizeWebSearchRouting(routing);
5957
6253
  const owner = currentSearchInitiator(ctx);
5958
6254
  const selection = currentSearchSelection(owner);
5959
6255
  const managedOwned = selection?.provider === "github-copilot-preview" && isPluginPreviewProvider(ctx, selection.provider);
5960
- if (!cfg.enabled || cfg.routeWebSearch === false || owner === void 0) return delegate(request, signal);
6256
+ if (!cfg.enabled || cfg.routeWebSearch === false) return delegate(request, signal);
6257
+ if (!policy.legacy && captureSearchProvider === void 0) throw new WebError("explicit search routing requires captureSearchProvider from the routed Web facade", "WEB_PROVIDER_UNAVAILABLE");
6258
+ if (owner === void 0) {
6259
+ if (!policy.legacy) throw new WebError("explicit search routing requires agents.currentInitiator()", "WEB_PROVIDER_UNAVAILABLE");
6260
+ return delegate(request, signal);
6261
+ }
6262
+ if (captureSearchProvider !== void 0) {
6263
+ if (policy.primaryProvider === "none") throw new WebError("web search is disabled by the primary search provider setting", "WEB_PROVIDER_UNAVAILABLE");
6264
+ const startedGeneration = generation;
6265
+ const registrations = [];
6266
+ let usingManagedPrimary = false;
6267
+ let configuredProofInvalid = false;
6268
+ const managedPreview = managedOwned ? ctx.get("githubCopilotPreview") : void 0;
6269
+ const managedProof = managedPreview?.captureSearchProof();
6270
+ const capture = (id) => {
6271
+ const provider = captureSearchProvider(id);
6272
+ if (provider !== void 0) registrations.push(provider);
6273
+ return provider;
6274
+ };
6275
+ const missing = (id) => ({
6276
+ id,
6277
+ search: async () => {
6278
+ throw new WebError("the configured search provider is not registered", "WEB_PROVIDER_CONFIGURED_MISSING");
6279
+ }
6280
+ });
6281
+ const configured = (id) => {
6282
+ const registration = capture(id);
6283
+ if (registration === void 0) return missing(id);
6284
+ if (id !== "github-copilot-hosted" || !registration.owns(traditionalProvider)) return registration;
6285
+ return {
6286
+ id,
6287
+ search: async (query, querySignal) => {
6288
+ try {
6289
+ return await configuredSearch(query, querySignal);
6290
+ } catch (error) {
6291
+ if (error instanceof ConfiguredSearchProofInvalidated) configuredProofInvalid = true;
6292
+ throw error;
6293
+ }
6294
+ }
6295
+ };
6296
+ };
6297
+ let primary;
6298
+ if (policy.primaryProvider === "auto") {
6299
+ if (isCopilotSearchSelection(selection, managedOwned) && (cfg.providers.length === 0 || cfg.providers.includes(selection?.provider ?? ""))) {
6300
+ const native = capture(GITHUB_COPILOT_HOSTED_SEARCH_PROVIDER_ID);
6301
+ if (native?.owns(traditionalProvider)) {
6302
+ usingManagedPrimary = managedOwned;
6303
+ primary = native;
6304
+ }
6305
+ } else if (selection !== void 0) primary = capture(selection.provider);
6306
+ } else primary = configured(policy.primaryProvider);
6307
+ const fallback = policy.defaultProvider === "none" ? void 0 : configured(policy.defaultProvider);
6308
+ const boundSignal = AbortSignal.any([
6309
+ ...signal === void 0 ? [] : [signal],
6310
+ proofCancellation.signal,
6311
+ plansFor(owner).cancellation.signal,
6312
+ ...registrations.map((provider) => provider.signal)
6313
+ ]);
6314
+ const canContinue = () => active && generation === startedGeneration && !disposedOwners.has(owner) && !configuredProofInvalid && registrations.every((provider) => provider.current()) && (!usingManagedPrimary || ctx.get("githubCopilotPreview") === managedPreview && managedProof?.() === true);
6315
+ return routeSearchTools(request, boundSignal, {
6316
+ primary,
6317
+ fallback,
6318
+ canContinue,
6319
+ allowFailureFallback: !policy.legacy || cfg.searchFallback !== "none"
6320
+ });
6321
+ }
5961
6322
  const nativeAllowed = isCopilotSearchSelection(selection, managedOwned) && (cfg.providers.length === 0 || cfg.providers.includes(selection?.provider ?? ""));
5962
6323
  const useNative = (routing.searchMode ?? "auto") === "auto" && nativeAllowed && (selectProvider === void 0 || traditionalAvailable());
5963
6324
  const defaultProviderId = routing.defaultSearchProvider?.trim() || "deepseek-official";
@@ -6005,7 +6366,7 @@ function activate(ctx, config) {
6005
6366
  });
6006
6367
  ctx.on("llm/stream", (request, next) => {
6007
6368
  const cfg = current();
6008
- if (!active || routingCurrent().searchMode === "fixed") return next();
6369
+ if (!active || normalizeWebSearchRouting(routingCurrent()).primaryProvider !== "auto") return next();
6009
6370
  const owner = currentSearchInitiator(ctx);
6010
6371
  if (owner !== void 0 && disposedOwners.has(owner)) return next();
6011
6372
  const route = currentChatRoute(ctx, request);
@@ -6024,7 +6385,8 @@ function activate(ctx, config) {
6024
6385
  text: () => ""
6025
6386
  });
6026
6387
  return (owner, selection) => {
6027
- if (!active || owner === void 0 || disposedOwners.has(owner) || routingCurrent().searchMode === "fixed") return "";
6388
+ const policy = normalizeWebSearchRouting(routingCurrent());
6389
+ if (!active || owner === void 0 || disposedOwners.has(owner) || policy.primaryProvider !== "auto") return "";
6028
6390
  const route = currentChatRoute(ctx, selection);
6029
6391
  const cfg = current();
6030
6392
  if (route === void 0 || cfg.providers.length > 0 && !cfg.providers.includes(route.provider)) return "";
@@ -6032,7 +6394,7 @@ function activate(ctx, config) {
6032
6394
  const nativeGuidance = servingPrompt(current, matches(cached, route, candidatesForRoute(route), cfg) ? cached.plan : void 0);
6033
6395
  const owned = route.provider === "github-copilot-preview" && isPluginPreviewProvider(ctx, route.provider);
6034
6396
  if (!cfg.enabled || cfg.routeWebSearch === false || ctx.get("githubCopilotOriginalWeb") === void 0 || !isCopilotSearchSelection(route, owned)) return nativeGuidance;
6035
- return [nativeGuidance, cfg.searchFallback === "none" ? "Copilot search fallback is disabled. Do not silently substitute another paid search backend after a search failure." : "When web_search reports a fallback, explicitly tell the user the actual search backend (and custom endpoint/model when provided) and possible API charges in your answer. Never describe fallback results as Copilot search. Automatic fallback does not need per-search confirmation."].filter(Boolean).join("\n\n");
6397
+ return [nativeGuidance, policy.defaultProvider === "none" || policy.legacy && cfg.searchFallback === "none" ? "Copilot search fallback is disabled. Do not silently substitute another paid search backend after a search failure." : "When web_search reports a fallback, explicitly tell the user the actual search backend (and custom endpoint/model when provided) and possible API charges in your answer. Never describe fallback results as Copilot search. Automatic fallback does not need per-search confirmation."].filter(Boolean).join("\n\n");
6036
6398
  };
6037
6399
  }
6038
6400
  /**