dsh-oauth-login 0.2.4

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 (64) hide show
  1. package/INSTALL.md +59 -0
  2. package/INSTALL.zh.md +45 -0
  3. package/LICENSE +201 -0
  4. package/README.en.md +104 -0
  5. package/README.md +104 -0
  6. package/README.zh.md +3 -0
  7. package/SECURITY.md +33 -0
  8. package/cordis.patch.yml +5 -0
  9. package/docs/network-proxy.md +45 -0
  10. package/docs/openrouter-sync.md +84 -0
  11. package/docs/screenshots/01-providers-signed-out.png +0 -0
  12. package/docs/screenshots/03-copilot-signing-in.png +0 -0
  13. package/docs/screenshots/04-mixed-live-states.png +0 -0
  14. package/docs/screenshots/05-copilot-sign-in.gif +0 -0
  15. package/docs/screenshots/06-cli-status-store.png +0 -0
  16. package/lib/bin.d.ts +5 -0
  17. package/lib/bin.js +2132 -0
  18. package/lib/client.js +1915 -0
  19. package/lib/client.js.map +1 -0
  20. package/lib/index.d.ts +482 -0
  21. package/lib/index.js +3105 -0
  22. package/lib/invariant.d.ts +7 -0
  23. package/lib/invariant.js +8 -0
  24. package/package.json +180 -0
  25. package/src/adapter.ts +207 -0
  26. package/src/auth-routes.ts +654 -0
  27. package/src/auth.ts +76 -0
  28. package/src/bin.ts +185 -0
  29. package/src/catalog.ts +129 -0
  30. package/src/client/OpenRouterSyncStatus.tsx +66 -0
  31. package/src/client/PiLoginSettings.tsx +470 -0
  32. package/src/client/ProxySettings.tsx +123 -0
  33. package/src/client/SearchableModelSelect.tsx +595 -0
  34. package/src/client/draft-input.ts +12 -0
  35. package/src/client/index.tsx +71 -0
  36. package/src/client/locales.ts +176 -0
  37. package/src/client/login-input-copy.ts +34 -0
  38. package/src/client/login-window.ts +24 -0
  39. package/src/client/model-filter.ts +45 -0
  40. package/src/client/openrouter-store.ts +122 -0
  41. package/src/client/proxy-draft.ts +57 -0
  42. package/src/extra-models.ts +201 -0
  43. package/src/hosted-capture.ts +41 -0
  44. package/src/hosted-images.ts +154 -0
  45. package/src/http.ts +241 -0
  46. package/src/ids.ts +17 -0
  47. package/src/index.ts +183 -0
  48. package/src/invariant.ts +12 -0
  49. package/src/model-error-hint.ts +98 -0
  50. package/src/native-tools.ts +442 -0
  51. package/src/oauth-refresh.ts +49 -0
  52. package/src/openrouter-catalog.ts +245 -0
  53. package/src/openrouter-free.ts +76 -0
  54. package/src/openrouter-models.ts +155 -0
  55. package/src/openrouter-types.ts +40 -0
  56. package/src/plugin-config.ts +30 -0
  57. package/src/provider.ts +137 -0
  58. package/src/proxy-config.ts +61 -0
  59. package/src/proxy-store.ts +45 -0
  60. package/src/proxy-transport.ts +220 -0
  61. package/src/redact.ts +22 -0
  62. package/src/responses-tap.ts +97 -0
  63. package/src/session.ts +104 -0
  64. package/src/store.ts +234 -0
@@ -0,0 +1,7 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+ //#region src/invariant.d.ts
3
+ declare const name = "pi-login-invariant";
4
+ declare const inject: string[];
5
+ declare const apply: (ctx: Context) => Promise<() => void>;
6
+ //#endregion
7
+ export { apply, inject, name };
@@ -0,0 +1,8 @@
1
+ //#region src/invariant.ts
2
+ const PACKAGE_NAME = "dsh-oauth-login";
3
+ const name = "pi-login-invariant";
4
+ const inject = ["invariants"];
5
+ const install = () => {};
6
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
7
+ //#endregion
8
+ export { apply, inject, name };
package/package.json ADDED
@@ -0,0 +1,180 @@
1
+ {
2
+ "name": "dsh-oauth-login",
3
+ "description": "DSH-owned subscription login powered by Pi AI adapters: OAuth plus official Plan API keys, with an independent credential store.",
4
+ "version": "0.2.4",
5
+ "publishConfig": {
6
+ "access": "public",
7
+ "registry": "https://registry.npmjs.org/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/aa2246740/dsh-oauth-login.git"
12
+ },
13
+ "homepage": "https://github.com/aa2246740/dsh-oauth-login#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/aa2246740/dsh-oauth-login/issues"
16
+ },
17
+ "keywords": [
18
+ "deepseek-harness",
19
+ "oauth",
20
+ "zhipu",
21
+ "glm",
22
+ "coding-plan",
23
+ "pi-agent",
24
+ "codex",
25
+ "openrouter"
26
+ ],
27
+ "engines": {
28
+ "node": ">=22.19.0"
29
+ },
30
+ "type": "module",
31
+ "main": "lib/index.js",
32
+ "types": "lib/index.d.ts",
33
+ "bin": {
34
+ "dsh-oauth-login": "lib/bin.js"
35
+ },
36
+ "scripts": {
37
+ "build": "npm run typecheck && tsdown",
38
+ "test": "vitest run",
39
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.client.json",
40
+ "check": "npm run typecheck && npm run test && npm run build",
41
+ "link:harness": "node scripts/link-harness-dependencies.mjs"
42
+ },
43
+ "exports": {
44
+ ".": {
45
+ "types": "./lib/index.d.ts",
46
+ "default": "./lib/index.js"
47
+ },
48
+ "./invariant": {
49
+ "types": "./lib/invariant.d.ts",
50
+ "default": "./lib/invariant.js"
51
+ },
52
+ "./client": "./lib/client.js",
53
+ "./cordis.patch.yml": "./cordis.patch.yml",
54
+ "./package.json": "./package.json"
55
+ },
56
+ "files": [
57
+ "lib",
58
+ "src",
59
+ "cordis.patch.yml",
60
+ "INSTALL.md",
61
+ "INSTALL.zh.md",
62
+ "README.md",
63
+ "README.en.md",
64
+ "README.zh.md",
65
+ "SECURITY.md",
66
+ "LICENSE",
67
+ "docs/screenshots",
68
+ "docs/openrouter-sync.md",
69
+ "docs/network-proxy.md"
70
+ ],
71
+ "license": "Apache-2.0",
72
+ "dependencies": {
73
+ "undici": "^8.5.0"
74
+ },
75
+ "dsh": {
76
+ "bundle": {
77
+ "patch": "./cordis.patch.yml"
78
+ },
79
+ "client": {
80
+ "inject": [
81
+ "@deepseek-ai/dsh-client-ui-conversation",
82
+ "@deepseek-ai/dsh-client-ui-model-selection",
83
+ "@deepseek-ai/dsh-client-ui-renderer",
84
+ "@deepseek-ai/dsh-client-ui-session",
85
+ "@deepseek-ai/dsh-client-ui-settings",
86
+ "@deepseek-ai/dsh-client-locale"
87
+ ],
88
+ "platform": "web"
89
+ }
90
+ },
91
+ "peerDependencies": {
92
+ "@deepseek-ai/cordis": "^4.0.2",
93
+ "@deepseek-ai/dsh-atomic-write": "*",
94
+ "@deepseek-ai/dsh-attachment": "*",
95
+ "@deepseek-ai/dsh-client-connection": ">=0.1.5-rc.3 <0.1.6",
96
+ "@deepseek-ai/dsh-home-paths": "*",
97
+ "@deepseek-ai/dsh-host-webserver": "*",
98
+ "@deepseek-ai/dsh-invariants": "*",
99
+ "@deepseek-ai/dsh-llm": "*",
100
+ "@deepseek-ai/dsh-llm-pi-ai": "*",
101
+ "@deepseek-ai/schemastery": "^3.18.2",
102
+ "@earendil-works/pi-ai": "^0.85.1",
103
+ "react": "^18.2.0"
104
+ },
105
+ "peerDependenciesMeta": {
106
+ "@earendil-works/pi-ai": {
107
+ "optional": true
108
+ },
109
+ "@deepseek-ai/cordis": {
110
+ "optional": true
111
+ },
112
+ "@deepseek-ai/dsh-atomic-write": {
113
+ "optional": true
114
+ },
115
+ "@deepseek-ai/dsh-attachment": {
116
+ "optional": true
117
+ },
118
+ "@deepseek-ai/dsh-client-connection": {
119
+ "optional": true
120
+ },
121
+ "@deepseek-ai/dsh-home-paths": {
122
+ "optional": true
123
+ },
124
+ "@deepseek-ai/dsh-host-webserver": {
125
+ "optional": true
126
+ },
127
+ "@deepseek-ai/dsh-invariants": {
128
+ "optional": true
129
+ },
130
+ "@deepseek-ai/dsh-llm": {
131
+ "optional": true
132
+ },
133
+ "@deepseek-ai/dsh-llm-pi-ai": {
134
+ "optional": true
135
+ },
136
+ "@deepseek-ai/schemastery": {
137
+ "optional": true
138
+ },
139
+ "react": {
140
+ "optional": true
141
+ }
142
+ },
143
+ "devDependencies": {
144
+ "@deepseek-ai/cordis": "^4.0.2",
145
+ "@deepseek-ai/dsh-api-session-controller": "0.1.5-rc.3",
146
+ "@deepseek-ai/dsh-atomic-write": "0.1.5-rc.3",
147
+ "@deepseek-ai/dsh-attachment": "0.1.5-rc.3",
148
+ "@deepseek-ai/dsh-authorization": "0.1.5-rc.3",
149
+ "@deepseek-ai/dsh-brand": "0.1.5-rc.3",
150
+ "@deepseek-ai/dsh-client-connection": "0.1.5-rc.3",
151
+ "@deepseek-ai/dsh-client-locale": "0.1.5-rc.3",
152
+ "@deepseek-ai/dsh-client-ui-conversation": "0.1.5-rc.3",
153
+ "@deepseek-ai/dsh-client-ui-model-selection": "0.1.5-rc.3",
154
+ "@deepseek-ai/dsh-client-ui-renderer": "0.1.5-rc.3",
155
+ "@deepseek-ai/dsh-client-ui-session": "0.1.5-rc.3",
156
+ "@deepseek-ai/dsh-client-ui-settings": "0.1.5-rc.3",
157
+ "@deepseek-ai/dsh-client-ui-slots": "0.1.5-rc.3",
158
+ "@deepseek-ai/dsh-credentials": "0.1.5-rc.3",
159
+ "@deepseek-ai/dsh-fs": "0.1.5-rc.3",
160
+ "@deepseek-ai/dsh-home-paths": "0.1.5-rc.3",
161
+ "@deepseek-ai/dsh-host-webserver": "0.1.5-rc.3",
162
+ "@deepseek-ai/dsh-invariants": "0.1.5-rc.3",
163
+ "@deepseek-ai/dsh-launch-environment": "0.1.5-rc.3",
164
+ "@deepseek-ai/dsh-llm": "0.1.5-rc.3",
165
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.5-rc.3",
166
+ "@deepseek-ai/dsh-settings": "0.1.5-rc.3",
167
+ "@deepseek-ai/dsh-timeout": "0.1.5-rc.3",
168
+ "@deepseek-ai/dsh-util-values": "0.1.5-rc.3",
169
+ "@deepseek-ai/schemastery": "3.18.2",
170
+ "@earendil-works/pi-ai": "0.85.1",
171
+ "@types/node": "^22.20.0",
172
+ "@types/react": "~18.3.1",
173
+ "@types/semver": "7.7.1",
174
+ "react": "^18.2.0",
175
+ "semver": "^7.7.2",
176
+ "tsdown": "^0.22.2",
177
+ "typescript": "^6.0.3",
178
+ "vitest": "^4.1.8"
179
+ }
180
+ }
package/src/adapter.ts ADDED
@@ -0,0 +1,207 @@
1
+ /** One PiAiAdapter covering every Pi-login harness route. */
2
+
3
+ import { LlmError, ReasoningEffortId, resolveRetryPolicy } from '@deepseek-ai/dsh-llm'
4
+ import { defaultProviderAuthContext } from '@earendil-works/pi-ai'
5
+ import type {
6
+ GenerateOptions,
7
+ LlmResolvedModelInfo,
8
+ PreparedAdapterCall,
9
+ RetryPolicyConfig,
10
+ StreamChunk,
11
+ } from '@deepseek-ai/dsh-llm'
12
+ import { PiAiAdapter } from '@deepseek-ai/dsh-llm-pi-ai'
13
+ import type {
14
+ PiAiAdapterOptions as BasePiAiAdapterOptions,
15
+ ResolvedPiAiProviderProfile,
16
+ } from '@deepseek-ai/dsh-llm-pi-ai'
17
+ import type { AttachmentStore } from '@deepseek-ai/dsh-attachment'
18
+ import { PI_LOGIN_PROVIDERS, piLoginProviderByRoute } from './catalog.ts'
19
+ import { defaultReasoningEffortFor } from './extra-models.ts'
20
+ import { iterateInCapture } from './hosted-capture.ts'
21
+ import type { HostedCapture } from './hosted-capture.ts'
22
+ import { injectHostedImages, stripAssistantImages } from './hosted-images.ts'
23
+ import {
24
+ PI_LOGIN_MAX_REQUEST_IMAGE_BYTES,
25
+ PI_LOGIN_STREAM_IDLE_TIMEOUT_MS,
26
+ } from './ids.ts'
27
+ import { hintFailure, withModelErrorHint } from './model-error-hint.ts'
28
+ import type { PiLoginSession } from './session.ts'
29
+ import { openRouterDefaultEffort } from './openrouter-models.ts'
30
+ import { OPENROUTER_ROUTE } from './openrouter-types.ts'
31
+ import { filterHostedServerToolTraces, nativePlanForRoute } from './native-tools.ts'
32
+ import type { NativeToolPolicy } from './native-tools.ts'
33
+
34
+ export interface PiLoginAdapterOptions {
35
+ streamIdleTimeoutMs?: number
36
+ retryPolicy?: RetryPolicyConfig
37
+ }
38
+
39
+ /** Keep provider failures routable for both direct and snapshot-prepared requests. */
40
+ async function* withFailureHints(source: AsyncIterable<StreamChunk>, provider: string): AsyncIterable<StreamChunk> {
41
+ try {
42
+ for await (const chunk of source) {
43
+ if (chunk.type === 'finish' && chunk.reason.kind === 'error') {
44
+ yield { ...chunk, reason: { ...chunk.reason, failure: hintFailure(chunk.reason.failure, provider) } }
45
+ } else {
46
+ yield chunk
47
+ }
48
+ }
49
+ } catch (error: unknown) {
50
+ throw withModelErrorHint(error, provider)
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Official Pi adapter plus bounded provider compatibility and Chat copy.
56
+ * `dsh-llm-retry` owns the attempt budget and routes on `code`. Finish chunks
57
+ * and thrown errors receive the same route-specific correction and hint.
58
+ */
59
+ class PiLoginAdapter extends PiAiAdapter {
60
+ constructor(
61
+ config: BasePiAiAdapterOptions,
62
+ private readonly native: NativeToolPolicy,
63
+ private readonly resolveAttachments: () => AttachmentStore | undefined,
64
+ private readonly session: PiLoginSession,
65
+ ) {
66
+ super(config)
67
+ }
68
+
69
+ override resolveModel(
70
+ provider: string,
71
+ model: string,
72
+ signal?: AbortSignal,
73
+ ): Promise<LlmResolvedModelInfo> {
74
+ return super.resolveModel(provider, model, signal).then((info) => {
75
+ const defaultEffort = this.defaultEffort(provider, model)
76
+ if (defaultEffort === undefined || info.reasoning === undefined) return info
77
+ return {
78
+ ...info,
79
+ reasoning: {
80
+ ...info.reasoning,
81
+ defaultEffort: ReasoningEffortId(defaultEffort),
82
+ },
83
+ }
84
+ })
85
+ }
86
+
87
+ private defaultEffort(provider: string, model: string): string | undefined {
88
+ return (provider === OPENROUTER_ROUTE ? openRouterDefaultEffort(this.session.openRouter.model(model)) : undefined)
89
+ ?? defaultReasoningEffortFor(model)
90
+ }
91
+
92
+ private sanitize(options: GenerateOptions): GenerateOptions {
93
+ const defaultEffort = this.defaultEffort(options.provider, options.model)
94
+ return {
95
+ ...options,
96
+ messages: stripAssistantImages(options.messages),
97
+ ...options.reasoningEffort === undefined && defaultEffort !== undefined
98
+ ? { reasoningEffort: ReasoningEffortId(defaultEffort) }
99
+ : {},
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Live `llm.stream` dispatches `prepareCall().stream`, not `adapter.stream()`.
105
+ * Hosted X Search / web_search traces must be stripped on that path too,
106
+ * or Harness executes them as UNKNOWN_TOOL.
107
+ */
108
+ private async * decorateStream(
109
+ options: GenerateOptions,
110
+ source: AsyncIterable<StreamChunk>,
111
+ ): AsyncIterable<StreamChunk> {
112
+ const capture: HostedCapture = { images: [] }
113
+ try {
114
+ const raw = iterateInCapture(capture, this.session.proxy.iterate(
115
+ source,
116
+ options.provider === 'pi-openai-codex' && options.sessionId !== undefined
117
+ ? String(options.sessionId)
118
+ : undefined,
119
+ ))
120
+ const spec = piLoginProviderByRoute(options.provider)
121
+ const api = spec === undefined
122
+ ? undefined
123
+ : this.session.visibleModels(spec.id).find(model => model.id === options.model)?.api
124
+ const plan = nativePlanForRoute(options.provider, api, this.native)
125
+ const filtered = plan === undefined ? raw : filterHostedServerToolTraces(raw)
126
+ const attachments = this.native.image ? this.resolveAttachments() : undefined
127
+ const decorated = attachments === undefined
128
+ ? filtered
129
+ : injectHostedImages(filtered, capture, input => attachments.saveImage(input))
130
+ yield* withFailureHints(decorated, options.provider)
131
+ } catch (error: unknown) {
132
+ throw withModelErrorHint(error, options.provider)
133
+ }
134
+ }
135
+
136
+ override async prepareCall(provider: string, model: string, signal?: AbortSignal): Promise<PreparedAdapterCall> {
137
+ const prepared = await this.session.proxy.run(() => super.prepareCall(provider, model, signal))
138
+ return {
139
+ ...prepared,
140
+ stream: options => {
141
+ const sanitized = this.sanitize(options)
142
+ return this.decorateStream(sanitized, prepared.stream(sanitized))
143
+ },
144
+ }
145
+ }
146
+
147
+ override async * stream(options: GenerateOptions): AsyncIterable<StreamChunk> {
148
+ const sanitized = this.sanitize(options)
149
+ yield* this.decorateStream(sanitized, super.stream(sanitized))
150
+ }
151
+ }
152
+
153
+ export function createPiLoginAdapter(
154
+ session: PiLoginSession,
155
+ resolveAttachments: () => AttachmentStore | undefined,
156
+ options: PiLoginAdapterOptions = {},
157
+ ): PiAiAdapter {
158
+ const streamIdleTimeoutMs = options.streamIdleTimeoutMs ?? PI_LOGIN_STREAM_IDLE_TIMEOUT_MS
159
+ const retryPolicy = resolveRetryPolicy(options.retryPolicy, 'dsh-oauth-login retryPolicy')
160
+ const requestImageLimits = {
161
+ requestImagePixelBudget: 2048 * 2048,
162
+ requestImageMaxBytes: 1024 * 1024,
163
+ }
164
+ const auth = {
165
+ auth: {
166
+ credentials: session.store,
167
+ authContext: defaultProviderAuthContext(),
168
+ },
169
+ }
170
+ return new PiLoginAdapter({
171
+ profiles: () => {
172
+ const profiles = new Map<string, ResolvedPiAiProviderProfile>()
173
+ for (const spec of PI_LOGIN_PROVIDERS) {
174
+ profiles.set(spec.route, {
175
+ provider: spec.route,
176
+ displayName: spec.displayName,
177
+ streamIdleTimeoutMs,
178
+ maxRequestImageBytes: PI_LOGIN_MAX_REQUEST_IMAGE_BYTES,
179
+ ...requestImageLimits,
180
+ retryPolicy,
181
+ configuredMaxTokens: new Map(),
182
+ modelErrors: new Map(),
183
+ piProvider: session.provider(spec.id),
184
+ })
185
+ }
186
+ return profiles
187
+ },
188
+ resolveApiKey: async (route) => {
189
+ await session.ensureTransport()
190
+ const spec = piLoginProviderByRoute(route)
191
+ if (spec === undefined) {
192
+ throw new LlmError(`dsh-oauth-login: unknown route "${route}"`, 'MISSING_CREDENTIAL')
193
+ }
194
+ const auth = await session.models.getAuth(spec.id)
195
+ const apiKey = auth?.auth.apiKey
196
+ if (apiKey === undefined || apiKey.length === 0) {
197
+ throw new LlmError(
198
+ `${spec.displayName} is not connected. Open Settings → Subscription Login and connect it.`,
199
+ 'MISSING_CREDENTIAL',
200
+ )
201
+ }
202
+ return apiKey
203
+ },
204
+ resolveAttachments,
205
+ ...auth,
206
+ }, session.native, resolveAttachments, session)
207
+ }