dsh-github-copilot 0.4.0-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/AGENTS.md +233 -0
  2. package/CONTRIBUTING.md +46 -0
  3. package/LICENSE +21 -0
  4. package/README.md +365 -0
  5. package/README.zh.md +376 -0
  6. package/SECURITY.md +29 -0
  7. package/cordis.patch.yml +16 -0
  8. package/deployment-baseline.json +752 -0
  9. package/docs/agent-readiness.md +95 -0
  10. package/docs/images/copilot-auth-card-signed-in.png +0 -0
  11. package/docs/images/copilot-device-code-copy.png +0 -0
  12. package/docs/images/copilot-model-freshness.png +0 -0
  13. package/docs/images/copilot-model-refreshing.png +0 -0
  14. package/docs/images/copilot-provider-authorization.png +0 -0
  15. package/docs/images/copilot-provider-entry.png +0 -0
  16. package/docs/images/github-copilot-auth-flow.gif +0 -0
  17. package/docs/model-compatibility-acceptance.md +46 -0
  18. package/docs/npm-distribution.md +134 -0
  19. package/docs/session-search-routing.md +53 -0
  20. package/docs/single-route-migration.md +115 -0
  21. package/lib/client.js +7153 -0
  22. package/lib/client.js.map +1 -0
  23. package/lib/index.js +5384 -0
  24. package/lib/remote.js +162 -0
  25. package/lib/routed-web.js +80 -0
  26. package/lib/search-routing-pFLux0W7.js +763 -0
  27. package/lib/types/account-model-auth.d.ts +15 -0
  28. package/lib/types/account-model-catalog.d.ts +67 -0
  29. package/lib/types/account-model-source.d.ts +85 -0
  30. package/lib/types/authorization-controller.d.ts +100 -0
  31. package/lib/types/client.d.ts +106 -0
  32. package/lib/types/compact-account.d.ts +42 -0
  33. package/lib/types/compatibility.d.ts +17 -0
  34. package/lib/types/config.d.ts +42 -0
  35. package/lib/types/content-file.d.ts +10 -0
  36. package/lib/types/copilot-auth.d.ts +23 -0
  37. package/lib/types/copilot-grant.d.ts +11 -0
  38. package/lib/types/copilot-identity.d.ts +6 -0
  39. package/lib/types/copilot-request.d.ts +11 -0
  40. package/lib/types/current-provider.d.ts +59 -0
  41. package/lib/types/deepseek-search-fallback.d.ts +11 -0
  42. package/lib/types/failure.d.ts +38 -0
  43. package/lib/types/http.d.ts +48 -0
  44. package/lib/types/index.d.ts +39 -0
  45. package/lib/types/migration-status.d.ts +50 -0
  46. package/lib/types/model-protocol.d.ts +31 -0
  47. package/lib/types/pi-provider-bridge.d.ts +22 -0
  48. package/lib/types/plan.d.ts +148 -0
  49. package/lib/types/preview-provider.d.ts +35 -0
  50. package/lib/types/preview-route.d.ts +63 -0
  51. package/lib/types/probe.d.ts +39 -0
  52. package/lib/types/reasoning-presentation.d.ts +55 -0
  53. package/lib/types/remote.d.ts +151 -0
  54. package/lib/types/responses-reasoning-text.d.ts +24 -0
  55. package/lib/types/responses-reasoning.d.ts +7 -0
  56. package/lib/types/route-ownership.d.ts +70 -0
  57. package/lib/types/routed-web.d.ts +38 -0
  58. package/lib/types/search-backend.d.ts +16 -0
  59. package/lib/types/search-routing.d.ts +43 -0
  60. package/lib/types/serialize.d.ts +109 -0
  61. package/lib/types/sse.d.ts +28 -0
  62. package/lib/types/temporary-models.d.ts +25 -0
  63. package/lib/types/tool-schema-compat.d.ts +17 -0
  64. package/lib/types/traditional-search.d.ts +17 -0
  65. package/lib/types/types.d.ts +69 -0
  66. package/lib/types/usage.d.ts +26 -0
  67. package/lib/types/watchdog.d.ts +22 -0
  68. package/lib/types/web-delegate.d.ts +12 -0
  69. package/lib/types/web-search-routing-card.d.ts +10 -0
  70. package/lib/types/web-search-routing-config.d.ts +16 -0
  71. package/lib/types/wire-anthropic.d.ts +37 -0
  72. package/lib/types/wire.d.ts +56 -0
  73. package/lib/web-delegate.js +9 -0
  74. package/package.json +200 -0
  75. package/scripts/check-search-composition.mjs +191 -0
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Idle watchdog for the inline wire path: aborts when no data arrives for a
3
+ * configurable interval. The timeout resets on every `reset()` call, so a
4
+ * slow but alive stream never trips it.
5
+ * @module dsh-github-copilot/watchdog
6
+ */
7
+ /** Abort-triggering idle timer. */
8
+ export declare class IdleWatchdog {
9
+ private readonly ms;
10
+ readonly signal: AbortSignal;
11
+ private readonly controller;
12
+ private timer;
13
+ /**
14
+ * @param ms - the idle bound in milliseconds.
15
+ */
16
+ constructor(ms: number);
17
+ /** Restart the idle window. Call after every received event. */
18
+ reset(): void;
19
+ /** Stop the timer and release the signal. */
20
+ dispose(): void;
21
+ }
22
+ //# sourceMappingURL=watchdog.d.ts.map
@@ -0,0 +1,12 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { WebRuntime } from '@deepseek-ai/dsh-web';
3
+ declare module '@deepseek-ai/cordis' {
4
+ interface Context {
5
+ githubCopilotOriginalWeb: WebRuntime;
6
+ }
7
+ }
8
+ /** Expose the unchanged official service from the bundle's named web isolate. */
9
+ export declare const name = "github-copilot-web-delegate";
10
+ export declare const inject: string[];
11
+ export declare function apply(ctx: Context): void;
12
+ //# sourceMappingURL=web-delegate.d.ts.map
@@ -0,0 +1,10 @@
1
+ import type { Context as ClientContext } from '@deepseek-ai/cordis';
2
+ import type { ReactElement } from 'react';
3
+ interface SearchRoutingCardProps {
4
+ readonly settings: ClientContext['remote']['settings'];
5
+ readonly copilot: ClientContext['remote']['githubCopilot'];
6
+ }
7
+ /** Models-page card for plugin-owned cross-provider search routing. */
8
+ export declare function WebSearchRoutingCard(props: SearchRoutingCardProps): ReactElement;
9
+ export {};
10
+ //# sourceMappingURL=web-search-routing-card.d.ts.map
@@ -0,0 +1,16 @@
1
+ import z from '@deepseek-ai/schemastery';
2
+ import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
3
+ /** Plugin-owned settings namespace for cross-provider web-search routing. */
4
+ export declare const WEB_SEARCH_ROUTING_SETTINGS_NAMESPACE: SettingsNamespace;
5
+ /** Sentinel that disables the default/fixed search backend. */
6
+ export declare const NO_DEFAULT_SEARCH_PROVIDER = "none";
7
+ /** Cross-provider policy applied by this plugin's routed web facade. */
8
+ export interface WebSearchRoutingConfig {
9
+ /** Auto prefers the initiating model's native search; fixed always uses the configured provider. */
10
+ searchMode?: 'auto' | 'fixed';
11
+ /** Registered search-provider id used by fixed mode and as auto mode's fallback. */
12
+ defaultSearchProvider?: string;
13
+ }
14
+ /** User-facing routing settings; provider-specific credentials and models remain provider-owned. */
15
+ export declare const WebSearchRoutingConfigSchema: z<WebSearchRoutingConfig>;
16
+ //# sourceMappingURL=web-search-routing-config.d.ts.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Anthropic Messages wire for the inline web search plugin: intercepts an
3
+ * agent-loop model call, builds a Messages streaming request carrying the
4
+ * server-side `web_search_20250305` tool, and maps the Anthropic event
5
+ * stream onto the harness StreamChunk contract. Server-side search blocks
6
+ * (`server_tool_use` / `web_search_tool_result`) are skipped — the gateway
7
+ * executes the search inside the same request and the model answers from
8
+ * its results in the same turn. The generator NEVER throws; every failure
9
+ * becomes a finish chunk.
10
+ * @module dsh-github-copilot/wire-anthropic
11
+ */
12
+ import type { GenerateOptions, StreamChunk, TokenUsage } from '@deepseek-ai/dsh-llm';
13
+ import type { SearchPlanCandidate } from './plan.ts';
14
+ import type { InlineConfig } from './config.ts';
15
+ import type { InlineHooks } from './wire.ts';
16
+ /** Map an Anthropic usage object onto the harness TokenUsage shape. */
17
+ export declare function mapAnthropicUsage(usage: {
18
+ readonly input_tokens?: number;
19
+ readonly output_tokens?: number;
20
+ readonly cache_read_input_tokens?: number;
21
+ readonly cache_creation_input_tokens?: number;
22
+ readonly output_tokens_details?: {
23
+ readonly thinking_tokens?: number;
24
+ };
25
+ }): TokenUsage;
26
+ /**
27
+ * Run one inline model call through the Anthropic Messages API with the
28
+ * server-side web search tool injected. Never throws; always ends with a
29
+ * finish chunk.
30
+ * @param request - the frozen agent-loop request.
31
+ * @param candidate - the settled endpoint facts (probe-verified).
32
+ * @param hooks - credential hooks.
33
+ * @param cfg - the current plugin configuration.
34
+ * @returns the StreamChunk stream for the loop's assembler.
35
+ */
36
+ export declare function inlineAnthropicStream(request: GenerateOptions, candidate: SearchPlanCandidate, hooks: InlineHooks, cfg: InlineConfig): AsyncGenerator<StreamChunk>;
37
+ //# sourceMappingURL=wire-anthropic.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The short-circuit wire: intercepts an agent-loop model call, builds a
3
+ * streaming request carrying the server-side `web_search` tool, and maps the
4
+ * SSE events onto the harness StreamChunk contract. The generator NEVER
5
+ * throws — every failure becomes a finish chunk, exactly what the loop's
6
+ * catch-less `for await` requires. {@link inlineWireStream} settles the plan
7
+ * first and then dispatches to the protocol the VERIFIED candidate speaks, so
8
+ * the wire always matches the probe verdict (never the chat route snapshot).
9
+ *
10
+ * Server-side `web_search_call` items are skipped: the gateway executes the
11
+ * search inside the same request and the model answers from its results in
12
+ * the same turn, so the harness never sees a tool call. (UI search cards
13
+ * were removed — the added latency outweighed the visual value.)
14
+ * @module dsh-github-copilot/wire
15
+ */
16
+ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
17
+ import type { SearchPlan, SearchPlanCandidate } from './plan.ts';
18
+ import type { ResponsesReasoningOptions } from './serialize.ts';
19
+ import type { ResolvedRequestAuth } from './copilot-request.ts';
20
+ import type { InlineConfig } from './config.ts';
21
+ /** Credential and cancellation hooks the stream resolves per operation. */
22
+ export interface InlineHooks {
23
+ /** Resolve one credential reference through the credentials seam. */
24
+ resolveApiKey: (candidate: SearchPlanCandidate) => Promise<string | ResolvedRequestAuth | undefined>;
25
+ /** Validate effective model/profile reasoning and return its mapped wire value. */
26
+ resolveResponsesReasoning?: (request: GenerateOptions, candidate: SearchPlanCandidate) => ResponsesReasoningOptions | undefined;
27
+ }
28
+ /**
29
+ * Whether the request carries an image attachment. Image blocks retain the
30
+ * harness attachment object that the official vision channel must consume.
31
+ */
32
+ export declare function contentHasImageAttachments(request: GenerateOptions): boolean;
33
+ /**
34
+ * Run one inline model call through whichever wire the settled candidate
35
+ * speaks. The plan is settled HERE — once — so the protocol choice follows
36
+ * the probe verdict instead of the chat route snapshot taken before the
37
+ * probe ran.
38
+ * @param request - the frozen agent-loop request.
39
+ * @param plan - the search plan owning endpoint facts.
40
+ * @param hooks - credential hooks.
41
+ * @param cfg - the current plugin configuration.
42
+ * @returns the StreamChunk stream for the loop's assembler.
43
+ */
44
+ export declare function inlineWireStream(request: GenerateOptions, plan: SearchPlan, hooks: InlineHooks, cfg: InlineConfig): AsyncGenerator<StreamChunk>;
45
+ /**
46
+ * Run one inline model call: fetch the Responses endpoint with the
47
+ * server-side web tool injected and translate the SSE stream into harness
48
+ * chunks. Never throws; always ends with a finish chunk.
49
+ * @param request - the frozen agent-loop request.
50
+ * @param candidate - the settled endpoint facts (probe-verified).
51
+ * @param hooks - credential hooks.
52
+ * @param cfg - the current plugin configuration.
53
+ * @returns the StreamChunk stream for the loop's assembler.
54
+ */
55
+ export declare function inlineStream(request: GenerateOptions, candidate: SearchPlanCandidate, hooks: InlineHooks, cfg: InlineConfig): AsyncGenerator<StreamChunk>;
56
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1,9 @@
1
+ //#region lib/types/web-delegate.js
2
+ /** Expose the unchanged official service from the bundle's named web isolate. */
3
+ const name = "github-copilot-web-delegate";
4
+ const inject = ["web"];
5
+ function apply(ctx) {
6
+ ctx.provide("githubCopilotOriginalWeb", ctx.web);
7
+ }
8
+ //#endregion
9
+ export { apply, inject, name };
package/package.json ADDED
@@ -0,0 +1,200 @@
1
+ {
2
+ "name": "dsh-github-copilot",
3
+ "description": "DSH companion for GitHub Copilot sign-in, account-aware model profiles, tool compatibility, and provider-hosted search.",
4
+ "version": "0.4.0-alpha.18",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/",
8
+ "tag": "alpha"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/cloga/dsh-github-copilot.git"
13
+ },
14
+ "type": "module",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/types/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./client": {
21
+ "types": "./lib/types/client.d.ts",
22
+ "default": "./lib/client.js"
23
+ },
24
+ "./remote": {
25
+ "types": "./lib/types/remote.d.ts",
26
+ "default": "./lib/remote.js"
27
+ },
28
+ "./web-delegate": {
29
+ "types": "./lib/types/web-delegate.d.ts",
30
+ "default": "./lib/web-delegate.js"
31
+ },
32
+ "./routed-web": {
33
+ "types": "./lib/types/routed-web.d.ts",
34
+ "default": "./lib/routed-web.js"
35
+ },
36
+ "./deployment-baseline.json": "./deployment-baseline.json",
37
+ "./package.json": "./package.json"
38
+ },
39
+ "files": [
40
+ "cordis.patch.yml",
41
+ "deployment-baseline.json",
42
+ "lib/*.js",
43
+ "lib/*.js.map",
44
+ "lib/types/**/*.d.ts",
45
+ "README.md",
46
+ "README.zh.md",
47
+ "CONTRIBUTING.md",
48
+ "SECURITY.md",
49
+ "AGENTS.md",
50
+ "docs/agent-readiness.md",
51
+ "docs/model-compatibility-acceptance.md",
52
+ "docs/single-route-migration.md",
53
+ "docs/session-search-routing.md",
54
+ "docs/npm-distribution.md",
55
+ "scripts/check-search-composition.mjs",
56
+ "docs/images/"
57
+ ],
58
+ "keywords": [
59
+ "dsh",
60
+ "deepseek-harness",
61
+ "plugin",
62
+ "cordis",
63
+ "github-copilot",
64
+ "oauth",
65
+ "model-provider",
66
+ "hosted-search",
67
+ "web-search",
68
+ "openai-responses",
69
+ "typescript"
70
+ ],
71
+ "license": "MIT",
72
+ "engines": {
73
+ "node": ">=22.19.0"
74
+ },
75
+ "peerDependencies": {
76
+ "@deepseek-ai/cordis": "^4.0.2",
77
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
78
+ "@deepseek-ai/dsh-agent-default-model": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
79
+ "@deepseek-ai/dsh-api-remotes": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
80
+ "@deepseek-ai/dsh-authorization": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
81
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
82
+ "@deepseek-ai/dsh-client-ui-settings-models": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
83
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
84
+ "@deepseek-ai/dsh-credentials": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
85
+ "@deepseek-ai/dsh-launch-environment": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
86
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
87
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
88
+ "@deepseek-ai/dsh-settings": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
89
+ "@deepseek-ai/dsh-typert-protocol": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
90
+ "@deepseek-ai/dsh-web": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
91
+ "@deepseek-ai/dsh-web-search-deepseek": "0.1.1-rc.2 || 0.1.2-rc.1 || 0.1.3-alpha.1 || 0.1.5-alpha.1 || 0.1.5-alpha.2 || 0.1.5-rc.1 || 0.1.5-rc.2",
92
+ "react": "^18.2.0"
93
+ },
94
+ "peerDependenciesMeta": {
95
+ "@deepseek-ai/dsh-web-search-deepseek": {
96
+ "optional": true
97
+ },
98
+ "@deepseek-ai/dsh-launch-environment": {
99
+ "optional": true
100
+ },
101
+ "@deepseek-ai/dsh-api-remotes": {
102
+ "optional": true
103
+ },
104
+ "@deepseek-ai/dsh-authorization": {
105
+ "optional": true
106
+ },
107
+ "@deepseek-ai/dsh-client-ui-renderer": {
108
+ "optional": true
109
+ },
110
+ "@deepseek-ai/dsh-client-ui-settings-models": {
111
+ "optional": true
112
+ },
113
+ "@deepseek-ai/dsh-client-ui-slots": {
114
+ "optional": true
115
+ },
116
+ "@deepseek-ai/dsh-typert-protocol": {
117
+ "optional": true
118
+ }
119
+ },
120
+ "dependencies": {
121
+ "@deepseek-ai/dsh-authorization": "0.1.2-rc.1",
122
+ "@deepseek-ai/schemastery": "^3.18.2",
123
+ "@earendil-works/pi-ai": "0.85.1",
124
+ "zod": "^4.4.3"
125
+ },
126
+ "devDependencies": {
127
+ "@deepseek-ai/cordis": "^4.0.2",
128
+ "@deepseek-ai/dsh-agent": "0.1.2-rc.1",
129
+ "@deepseek-ai/dsh-agent-default-model": "0.1.2-rc.1",
130
+ "@deepseek-ai/dsh-api-gateway": "0.1.2-rc.1",
131
+ "@deepseek-ai/dsh-api-remotes": "0.1.2-rc.1",
132
+ "@deepseek-ai/dsh-app-boot": "0.1.2-rc.1",
133
+ "@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
134
+ "@deepseek-ai/dsh-authorization": "0.1.2-rc.1",
135
+ "@deepseek-ai/dsh-client-connection": "0.1.2-rc.1",
136
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.2-rc.1",
137
+ "@deepseek-ai/dsh-client-ui-settings-models": "0.1.2-rc.1",
138
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.2-rc.1",
139
+ "@deepseek-ai/dsh-credentials": "0.1.2-rc.1",
140
+ "@deepseek-ai/dsh-fs": "0.1.2-rc.1",
141
+ "@deepseek-ai/dsh-launch-environment": "0.1.2-rc.1",
142
+ "@deepseek-ai/dsh-llm": "0.1.2-rc.1",
143
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.2-rc.1",
144
+ "@deepseek-ai/dsh-scope": "0.1.2-rc.1",
145
+ "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
146
+ "@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
147
+ "@deepseek-ai/dsh-tool-web": "0.1.2-rc.1",
148
+ "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
149
+ "@deepseek-ai/dsh-typert-protocol": "0.1.2-rc.1",
150
+ "@deepseek-ai/dsh-typert-registry": "0.1.2-rc.1",
151
+ "@deepseek-ai/dsh-web": "0.1.2-rc.1",
152
+ "@deepseek-ai/dsh-web-search-deepseek": "0.1.2-rc.1",
153
+ "@types/node": "^22.0.0",
154
+ "@types/react": "~18.3.1",
155
+ "jsdom": "^26.1.0",
156
+ "react": "^18.2.0",
157
+ "tsdown": "^0.22.2",
158
+ "typescript": "^5.9.0",
159
+ "vitest": "^3.2.0"
160
+ },
161
+ "main": "lib/index.js",
162
+ "types": "lib/types/index.d.ts",
163
+ "imports": {
164
+ "#package.json": "./package.json"
165
+ },
166
+ "dsh": {
167
+ "bundle": {
168
+ "patch": "./cordis.patch.yml"
169
+ },
170
+ "client": {
171
+ "inject": [
172
+ "@deepseek-ai/dsh-api-remotes",
173
+ "@deepseek-ai/dsh-client-ui-settings-models"
174
+ ],
175
+ "platform": "web"
176
+ }
177
+ },
178
+ "scripts": {
179
+ "build": "pnpm clean && tsc -p tsconfig.json && tsdown",
180
+ "clean": "node -e \"require('node:fs').rmSync('lib', { recursive: true, force: true })\"",
181
+ "test": "vitest run",
182
+ "test:watch": "vitest",
183
+ "typecheck": "tsc -p tsconfig.json --noEmit",
184
+ "typecheck:tests": "tsc -p tsconfig.tests.json",
185
+ "test:scripts": "node --test \"tests/scripts/*.test.mjs\"",
186
+ "agent:describe": "node scripts/agent.mjs describe --json",
187
+ "agent:doctor": "node scripts/agent.mjs doctor --json",
188
+ "agent:plan": "node scripts/agent.mjs plan",
189
+ "preflight:search": "node scripts/check-search-composition.mjs",
190
+ "verify:agent": "node scripts/verify-agent-contract.mjs",
191
+ "verify:tarball": "node scripts/verify-tarball.mjs",
192
+ "verify:baseline": "node scripts/verify-deployment-baseline.mjs",
193
+ "verify:package": "node scripts/verify-package.mjs",
194
+ "verify:controlled-core": "node scripts/verify-controlled-core.mjs",
195
+ "verify:reasoning-ui": "node scripts/verify-reasoning-presentation.mjs",
196
+ "verify:upstream": "node scripts/verify-upstream-baseline.mjs",
197
+ "release:check": "node scripts/release-policy.mjs",
198
+ "verify": "pnpm verify:agent && pnpm typecheck && pnpm typecheck:tests && pnpm verify:baseline && pnpm build && pnpm test && pnpm test:scripts && pnpm verify:package"
199
+ }
200
+ }
@@ -0,0 +1,191 @@
1
+ import { createRequire } from 'node:module'
2
+ import { isDeepStrictEqual } from 'node:util'
3
+ import { readFile } from 'node:fs/promises'
4
+ import { resolve, join, isAbsolute } from 'node:path'
5
+ import { fileURLToPath, pathToFileURL } from 'node:url'
6
+
7
+ const realm = 'github-copilot-original-web'
8
+ const reserved = new Set(['github-copilot-web-delegate', 'github-copilot-routed-web'])
9
+
10
+ /** Structural pre-install validation only; never imports/boots configured plugins. */
11
+ export function checkSearchComposition(entries) {
12
+ const reasons = new Set()
13
+ const matches = []
14
+ function walk(rows, depth) {
15
+ if (!Array.isArray(rows)) { reasons.add('INVALID_ENTRY_LIST'); return }
16
+ for (const entry of rows) {
17
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) { reasons.add('INVALID_ENTRY'); continue }
18
+ if (entry.id === 'web') matches.push({ entry, depth })
19
+ if (entry.name === '@deepseek-ai/dsh-web' && entry.id !== 'web') reasons.add('ADDITIONAL_OFFICIAL_WEB_SERVICE')
20
+ if (reserved.has(entry.id) || ['dsh-github-copilot/web-delegate', 'dsh-github-copilot/routed-web'].includes(entry.name)) reasons.add('ROUTING_ENTRY_CONFLICT')
21
+ if (entry.isolate !== undefined) {
22
+ if (!entry.isolate || typeof entry.isolate !== 'object' || Array.isArray(entry.isolate)) reasons.add('UNKNOWN_ISOLATION')
23
+ else {
24
+ if (Object.values(entry.isolate).some(value => value === realm)) reasons.add('ROUTING_REALM_CONFLICT')
25
+ if (Object.values(entry.isolate).some(value => typeof value !== 'boolean' && typeof value !== 'string')) reasons.add('UNKNOWN_ISOLATION')
26
+ }
27
+ }
28
+ if (entry.group === true) walk(entry.config, depth + 1)
29
+ else if (entry.group !== undefined && entry.group !== false) reasons.add('UNKNOWN_GROUP')
30
+ }
31
+ }
32
+ walk(entries, 0)
33
+ if (matches.length !== 1) reasons.add(matches.length === 0 ? 'WEB_ROW_MISSING' : 'MULTIPLE_WEB_ROWS')
34
+ for (const { entry, depth } of matches) {
35
+ if (depth !== 0) reasons.add('WEB_NOT_TOP_LEVEL')
36
+ if (entry.name !== '@deepseek-ai/dsh-web') reasons.add('CUSTOM_WEB_SERVICE')
37
+ if (entry.group === true || Object.keys(entry).some(key => !['id', 'name', 'config', 'disabled', 'group', 'isolate'].includes(key))) reasons.add('WEB_ENTRY_REQUIRES_REVIEW')
38
+ if (entry.disabled !== undefined && entry.disabled !== false) reasons.add('WEB_DISABLED_OR_DYNAMIC')
39
+ if (entry.config !== undefined && (!entry.config || typeof entry.config !== 'object' || Array.isArray(entry.config)
40
+ || Object.entries(entry.config).some(([key, value]) => !['searchProvider', 'fetchProvider'].includes(key) || typeof value !== 'string'))) reasons.add('WEB_CONFIG_REQUIRES_REVIEW')
41
+ if (entry.isolate !== undefined && (!entry.isolate || typeof entry.isolate !== 'object' || Array.isArray(entry.isolate) || Object.keys(entry.isolate).length > 0)) reasons.add('PREEXISTING_WEB_ISOLATION')
42
+ }
43
+ return { schemaVersion: 1, supported: reasons.size === 0, reasons: [...reasons].sort(), scope: 'pre-install-structural-only; no boot, credentials, network or writes' }
44
+ }
45
+
46
+ function reportCandidate(reasons) {
47
+ return { schemaVersion: 1, supported: reasons.size === 0, reasons: [...reasons].sort(), scope: 'candidate-structural-only; no boot, credentials, network or writes' }
48
+ }
49
+
50
+ function isRecord(value) {
51
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
52
+ }
53
+
54
+ function hasExpression(value) {
55
+ if (!value || typeof value !== 'object') return false
56
+ return Object.entries(value).some(([key, child]) => key === '__jsExpr' || key === '__js' || hasExpression(child))
57
+ }
58
+
59
+ function accountRows(entries) {
60
+ const matches = []
61
+ for (const entry of entries) {
62
+ if (!isRecord(entry)) continue
63
+ if (entry.id === 'github-copilot' || entry.name === 'dsh-github-copilot') matches.push(entry)
64
+ if (entry.group === true && Array.isArray(entry.config)) matches.push(...accountRows(entry.config))
65
+ }
66
+ return matches
67
+ }
68
+
69
+ /** Only the bundle's reviewed, static service wiring is automatically supported. */
70
+ function routingRowValid(entry, name, isolated, account = false) {
71
+ if (!entry || entry.name !== name) return false
72
+ const keys = ['id', 'name', 'disabled', 'group', 'isolate', ...(account ? ['config'] : [])]
73
+ if (Object.keys(entry).some(key => !keys.includes(key))) return false
74
+ if (entry.disabled !== undefined && entry.disabled !== false) return false
75
+ if (entry.group !== undefined && entry.group !== false) return false
76
+ if (isolated) {
77
+ if (!isDeepStrictEqual(entry.isolate, { web: realm })) return false
78
+ } else if (entry.isolate !== undefined && (!isRecord(entry.isolate) || Object.keys(entry.isolate).length !== 0)) return false
79
+ return entry.config === undefined || (account && isRecord(entry.config) && !hasExpression(entry.config))
80
+ }
81
+
82
+ function candidateViable(entries, baseline) {
83
+ const original = baseline.find(entry => entry.id === 'web')
84
+ const web = entries.find(entry => entry.id === 'web')
85
+ if (!original || !web || !isDeepStrictEqual(web.isolate, { web: realm })) return false
86
+ for (const [id, name, isolated, account] of [
87
+ ['github-copilot-web-delegate', 'dsh-github-copilot/web-delegate', true, false],
88
+ ['github-copilot-routed-web', 'dsh-github-copilot/routed-web', false, false],
89
+ ['github-copilot', 'dsh-github-copilot', false, true],
90
+ ]) {
91
+ const matches = entries.filter(entry => entry.id === id)
92
+ if (matches.length !== 1 || !routingRowValid(matches[0], name, isolated, account)) return false
93
+ }
94
+ // Apart from the two new service rows and the original web's realm, the
95
+ // candidate must preserve the entire routing-free tree, including user config.
96
+ // These are parsed, owned config objects, never live Loader/Service objects.
97
+ const restored = entries.filter(entry => !reserved.has(entry.id)).map(entry => {
98
+ if (entry.id !== 'web') return entry
99
+ const copy = { ...entry }
100
+ if (Object.hasOwn(original, 'isolate')) copy.isolate = original.isolate
101
+ else delete copy.isolate
102
+ return copy
103
+ })
104
+ return isDeepStrictEqual(restored, baseline)
105
+ }
106
+
107
+ /** Read and compose the proposed normal launch without loadProfile's initialization/normalization writes. */
108
+ export async function inspectProfileSearchComposition({ profileDir, home, installAnchor, patches = [] }, suppliedBoot) {
109
+ const native = suppliedBoot ?? await import(pathToFileURL(createRequire(installAnchor).resolve('@deepseek-ai/dsh-app-boot')).href)
110
+ const { readProfileManifest, resolveBundleDir, loadOverlayPatches, loadOptionalPatches, composeEntries } = native
111
+ const bin = 'copilot-search-preflight'
112
+ const manifest = readProfileManifest(bin, profileDir)
113
+ const reasons = new Set()
114
+ // Public CLI prepareProfile rewrites cordis.yml to [] before normal launch.
115
+ // Never certify rows that only exist in that disposable file, or rewrite it.
116
+ const root = loadOptionalPatches(bin, join(profileDir, 'cordis.yml'))
117
+ if (root?.length) return reportCandidate(new Set(['NONEMPTY_DISPOSABLE_PROFILE_ROOT']))
118
+ const candidate = loadOverlayPatches(bin, fileURLToPath(new URL('../cordis.patch.yml', import.meta.url)))
119
+ const layers = []
120
+ const baselineLayers = []
121
+ let copilotLayers = 0
122
+ let candidateIndex
123
+ function addCandidate() {
124
+ candidateIndex ??= layers.length
125
+ layers.push(candidate)
126
+ // The routing-free comparison retains the account row and its user config.
127
+ baselineLayers.push([{ insert: [{ id: 'github-copilot', name: 'dsh-github-copilot' }] }])
128
+ }
129
+ for (const name of manifest.dsh?.profile?.bundles ?? []) {
130
+ const directory = resolveBundleDir(bin, name, installAnchor, profileDir)
131
+ const bundle = readProfileManifest(bin, directory)
132
+ if (bundle.name === 'dsh-github-copilot') {
133
+ copilotLayers++
134
+ // Updating replaces our existing slot, not its position in bundle order.
135
+ addCandidate()
136
+ continue
137
+ }
138
+ if (typeof bundle.dsh?.bundle?.patch !== 'string') throw new Error('INVALID_BUNDLE_MANIFEST')
139
+ const layer = loadOverlayPatches(bin, resolve(directory, bundle.dsh.bundle.patch))
140
+ layers.push(layer)
141
+ baselineLayers.push(layer)
142
+ }
143
+ if (copilotLayers === 0) addCandidate()
144
+ const late = [
145
+ loadOptionalPatches(bin, join(profileDir, 'cordis.patch.yml')) ?? [],
146
+ loadOptionalPatches(bin, join(home, 'cordis.patch.yml')) ?? [],
147
+ ...patches.map(file => loadOverlayPatches(bin, file)),
148
+ ]
149
+ const warn = () => reasons.add('UNAPPLIED_PATCH_REQUIRES_REVIEW')
150
+ // Public composeEntries flattens each complete stack in one pass. Repeatedly
151
+ // patching a previously composed tree would change the include's id index.
152
+ const beforeGuard = composeEntries(layers.slice(0, candidateIndex), warn)
153
+ const baseline = composeEntries([...baselineLayers, ...late], warn)
154
+ for (const entries of [beforeGuard, baseline]) {
155
+ for (const reason of checkSearchComposition(entries).reasons) reasons.add(reason)
156
+ }
157
+ const entries = composeEntries([...layers, ...late], warn)
158
+ if (copilotLayers > 1 || accountRows(entries).length !== 1) reasons.add('DUPLICATE_COPILOT_ACCOUNT_ENTRY')
159
+ if (!candidateViable(entries, baseline)) reasons.add('CANDIDATE_ROUTING_NOT_VIABLE')
160
+ return reportCandidate(reasons)
161
+ }
162
+
163
+ function parse(args) {
164
+ const result = { patches: [] }
165
+ for (let index = 0; index < args.length; index += 2) {
166
+ const value = args[index + 1]
167
+ if (!value || !isAbsolute(value)) throw new Error('INVALID_ARGUMENTS')
168
+ const names = { '--profile-dir': 'profileDir', '--home': 'home', '--install-anchor': 'installAnchor' }
169
+ const key = Object.hasOwn(names, args[index]) ? names[args[index]] : undefined
170
+ if (args[index] === '--patch') result.patches.push(value)
171
+ else if (!key || result[key]) throw new Error('INVALID_ARGUMENTS')
172
+ else result[key] = value
173
+ }
174
+ if (!result.profileDir || !result.home || !result.installAnchor) throw new Error('INVALID_ARGUMENTS')
175
+ return result
176
+ }
177
+
178
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
179
+ try {
180
+ const options = parse(process.argv.slice(2))
181
+ // Refuse missing profiles before any public reader could initialize one.
182
+ await readFile(join(options.profileDir, 'package.json'), 'utf8')
183
+ const result = await inspectProfileSearchComposition(options)
184
+ console.log(JSON.stringify(result))
185
+ if (!result.supported) process.exitCode = 1
186
+ } catch {
187
+ // Parse/provider-config errors can contain credentials. Never echo them.
188
+ console.log(JSON.stringify({ schemaVersion: 1, supported: false, reasons: ['PREFLIGHT_INPUT_OR_PUBLIC_API_FAILED'], scope: 'read-only; no boot/install attempted' }))
189
+ process.exitCode = 2
190
+ }
191
+ }