dsh-oauth-login 0.2.4 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { LlmFailure, Message, RetryPolicyConfig, StreamChunk } from "@deepseek-ai/dsh-llm";
1
+ import { LlmFailure, RequestMessage, RetryPolicyConfig, StreamChunk } from "@deepseek-ai/dsh-llm";
2
2
  import { Api, AuthInteraction, AuthOperationOptions, Context, Credential, CredentialInfo, CredentialStore, Model, MutableModels, Provider, StreamOptions } from "@earendil-works/pi-ai";
3
3
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -395,7 +395,7 @@ declare function decodeHostedImage(base64: string): {
395
395
  * pi-ai replay cannot represent assistant ImageBlocks. Drop them before the
396
396
  * next request so replay metadata still lines up with remaining content.
397
397
  */
398
- declare function stripAssistantImages(messages: readonly Message[]): Message[];
398
+ declare function stripAssistantImages(messages: readonly RequestMessage[]): RequestMessage[];
399
399
  declare function injectHostedImages(source: AsyncIterable<StreamChunk>, capture: HostedCapture, save: (input: SaveImageAttachment) => Promise<ImageAttachmentRef>): AsyncGenerator<StreamChunk>;
400
400
  //#endregion
401
401
  //#region src/oauth-refresh.d.ts
package/lib/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { LlmError, ReasoningEffortId, RetryPolicySchema, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
2
- import { createModels, defaultProviderAuthContext } from "@earendil-works/pi-ai";
2
+ import { createModels, defaultProviderAuthContext, getCurrentTools, normalizeContext } from "@earendil-works/pi-ai";
3
3
  import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
5
  import { basename, dirname, join, resolve } from "node:path";
@@ -762,6 +762,10 @@ const OPENROUTER_REFRESH_MS = 9e5;
762
762
  const OPENROUTER_COOLDOWN_MS = 6e4;
763
763
  //#endregion
764
764
  //#region src/native-tools.ts
765
+ /**
766
+ * OAuth routes that already have provider-hosted search / image tools.
767
+ * DSH's web_search function tool would hide those and bill a second key.
768
+ */
765
769
  /** DSH model-facing tools that steal traffic from a subscribed provider. */
766
770
  const DSH_WEB_TOOL_NAMES = ["web_search", "web_fetch"];
767
771
  /** Matching tool-web prompt sections. */
@@ -1041,10 +1045,15 @@ function wrapOnPayload(existing, providerId, api, policy = DEFAULT_NATIVE_TOOL_P
1041
1045
  };
1042
1046
  }
1043
1047
  function filterPiContext(context, providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
1044
- if (nativePlan(providerId, api, policy) === void 0 || context.tools === void 0) return context;
1048
+ if (nativePlan(providerId, api, policy) === void 0) return context;
1045
1049
  return {
1046
1050
  ...context,
1047
- tools: context.tools.filter((tool) => !isDshWebToolName(tool.name))
1051
+ ...context.tools === void 0 ? {} : { tools: context.tools.filter((tool) => !isDshWebToolName(tool.name)) },
1052
+ messages: context.messages.map((message) => message.role !== "system" ? message : {
1053
+ ...message,
1054
+ ...message.toolsAdded === void 0 ? {} : { toolsAdded: message.toolsAdded.filter((tool) => !isDshWebToolName(tool.name)) },
1055
+ ...message.toolsRemoved === void 0 ? {} : { toolsRemoved: message.toolsRemoved.filter((tool) => !isDshWebToolName(tool.name)) }
1056
+ })
1048
1057
  };
1049
1058
  }
1050
1059
  /**
@@ -1056,7 +1065,7 @@ function filterPiContext(context, providerId, api, policy = DEFAULT_NATIVE_TOOL_
1056
1065
  * when the provider serializer later emits `tools: []`.
1057
1066
  */
1058
1067
  function prepareNativeToolRequest(context, options, providerId, api, policy = DEFAULT_NATIVE_TOOL_POLICY) {
1059
- if (context.tools === void 0 || nativePlan(providerId, api, policy) === void 0) return {
1068
+ if (context.tools === void 0 && getCurrentTools(context.messages).length === 0 || nativePlan(providerId, api, policy) === void 0) return {
1060
1069
  context,
1061
1070
  options
1062
1071
  };
@@ -1903,12 +1912,12 @@ function harnessProvider(spec, native = DEFAULT_NATIVE_TOOL_POLICY, catalog) {
1903
1912
  stream: (model, context, options) => {
1904
1913
  const request = prepareNativeToolRequest(context, options ?? {}, spec.id, model.api, native);
1905
1914
  const guarded = spec.id === "openrouter" ? prepareOpenRouterOptions(model, request.options, catalog) : request.options;
1906
- return base.stream(model, request.context, guarded);
1915
+ return base.stream(model, normalizeContext(request.context), guarded);
1907
1916
  },
1908
1917
  streamSimple: (model, context, options) => {
1909
1918
  const request = prepareNativeToolRequest(context, options ?? {}, spec.id, model.api, native);
1910
1919
  const guarded = spec.id === "openrouter" ? prepareOpenRouterOptions(model, request.options, catalog) : request.options;
1911
- return base.streamSimple(model, request.context, guarded);
1920
+ return base.streamSimple(model, normalizeContext(request.context), guarded);
1912
1921
  }
1913
1922
  };
1914
1923
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-oauth-login",
3
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",
4
+ "version": "0.2.7",
5
5
  "publishConfig": {
6
6
  "access": "public",
7
7
  "registry": "https://registry.npmjs.org/"
@@ -90,16 +90,17 @@
90
90
  },
91
91
  "peerDependencies": {
92
92
  "@deepseek-ai/cordis": "^4.0.2",
93
+ "@deepseek-ai/dsh": ">=0.2.0-rc.2 <0.2.1",
93
94
  "@deepseek-ai/dsh-atomic-write": "*",
94
95
  "@deepseek-ai/dsh-attachment": "*",
95
- "@deepseek-ai/dsh-client-connection": ">=0.1.5-rc.3 <0.1.6",
96
+ "@deepseek-ai/dsh-client-connection": ">=0.2.0-rc.2 <0.2.1",
96
97
  "@deepseek-ai/dsh-home-paths": "*",
97
98
  "@deepseek-ai/dsh-host-webserver": "*",
98
99
  "@deepseek-ai/dsh-invariants": "*",
99
100
  "@deepseek-ai/dsh-llm": "*",
100
101
  "@deepseek-ai/dsh-llm-pi-ai": "*",
101
102
  "@deepseek-ai/schemastery": "^3.18.2",
102
- "@earendil-works/pi-ai": "^0.85.1",
103
+ "@earendil-works/pi-ai": "^0.87.1",
103
104
  "react": "^18.2.0"
104
105
  },
105
106
  "peerDependenciesMeta": {
@@ -109,6 +110,9 @@
109
110
  "@deepseek-ai/cordis": {
110
111
  "optional": true
111
112
  },
113
+ "@deepseek-ai/dsh": {
114
+ "optional": true
115
+ },
112
116
  "@deepseek-ai/dsh-atomic-write": {
113
117
  "optional": true
114
118
  },
@@ -141,33 +145,33 @@
141
145
  }
142
146
  },
143
147
  "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",
148
+ "@deepseek-ai/cordis": "4.0.4",
149
+ "@deepseek-ai/dsh-api-session-controller": "0.2.0-rc.2",
150
+ "@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
151
+ "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
152
+ "@deepseek-ai/dsh-authorization": "0.2.0-rc.2",
153
+ "@deepseek-ai/dsh-brand": "0.2.0-rc.2",
154
+ "@deepseek-ai/dsh-client-connection": "0.2.0-rc.2",
155
+ "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
156
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
157
+ "@deepseek-ai/dsh-client-ui-model-selection": "0.2.0-rc.2",
158
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
159
+ "@deepseek-ai/dsh-client-ui-session": "0.2.0-rc.2",
160
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
161
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
162
+ "@deepseek-ai/dsh-credentials": "0.2.0-rc.2",
163
+ "@deepseek-ai/dsh-fs": "0.2.0-rc.2",
164
+ "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
165
+ "@deepseek-ai/dsh-host-webserver": "0.2.0-rc.2",
166
+ "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
167
+ "@deepseek-ai/dsh-launch-environment": "0.2.0-rc.2",
168
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
169
+ "@deepseek-ai/dsh-llm-pi-ai": "0.2.0-rc.2",
170
+ "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
171
+ "@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
172
+ "@deepseek-ai/dsh-util-values": "0.2.0-rc.2",
173
+ "@deepseek-ai/schemastery": "3.18.4",
174
+ "@earendil-works/pi-ai": "0.87.1",
171
175
  "@types/node": "^22.20.0",
172
176
  "@types/react": "~18.3.1",
173
177
  "@types/semver": "7.7.1",
@@ -322,18 +322,22 @@ export function SearchableModelSelect(
322
322
  close()
323
323
  }
324
324
 
325
- const settleSelection = (accepted: boolean): void => {
326
- if (accepted) {
325
+ const settleSelection = (result: Awaited<ReturnType<ModelSelectInjected['select']>>): void => {
326
+ if (result === undefined) return
327
+ if (result.ok) {
327
328
  if (rootRef.current !== null) close(true)
328
329
  return
329
330
  }
330
- const message = directory.getSnapshot().error
331
- if (message !== null) {
332
- noticeSeq.current += 1
333
- window.clearTimeout(noticeTimer.current)
334
- setNotice({ seq: noticeSeq.current, text: t('error.action', { message }) })
335
- noticeTimer.current = window.setTimeout(() => { setNotice(null) }, 4000)
336
- }
331
+ const { error } = result
332
+ noticeSeq.current += 1
333
+ window.clearTimeout(noticeTimer.current)
334
+ setNotice({
335
+ seq: noticeSeq.current,
336
+ text: error.code === 'session/writer-held'
337
+ ? t('error.sessionInUse')
338
+ : t('error.action', { message: `${error.code}: ${error.message}` }),
339
+ })
340
+ noticeTimer.current = window.setTimeout(() => { setNotice(null) }, 4000)
337
341
  }
338
342
 
339
343
  const choose = (selection: ModelSelection): void => {
@@ -62,8 +62,8 @@ export function apply(ctx: ClientContext): void {
62
62
  if (available) directory.load().catch(() => { /* surfaced on the store */ })
63
63
  },
64
64
  select: (selection: ModelSelection) => available
65
- ? directory.select(selection).then(() => true, () => false)
66
- : Promise.resolve(false),
65
+ ? directory.select(selection)
66
+ : Promise.resolve(undefined),
67
67
  ts,
68
68
  }
69
69
  },
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { ImageAttachmentRef, ImageMediaType, SaveImageAttachment } from '@deepseek-ai/dsh-attachment'
7
- import type { Message, StreamChunk } from '@deepseek-ai/dsh-llm'
7
+ import type { RequestMessage, StreamChunk } from '@deepseek-ai/dsh-llm'
8
8
  import type { HostedCapture, HostedImage } from './hosted-capture.ts'
9
9
 
10
10
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -107,7 +107,7 @@ function extensionFor(mediaType: ImageMediaType): string {
107
107
  * pi-ai replay cannot represent assistant ImageBlocks. Drop them before the
108
108
  * next request so replay metadata still lines up with remaining content.
109
109
  */
110
- export function stripAssistantImages(messages: readonly Message[]): Message[] {
110
+ export function stripAssistantImages(messages: readonly RequestMessage[]): RequestMessage[] {
111
111
  return messages.map(message => {
112
112
  if (message.role !== 'assistant') return message
113
113
  const content = message.content.filter(block => block.type !== 'image')
@@ -3,6 +3,7 @@
3
3
  * DSH's web_search function tool would hide those and bill a second key.
4
4
  */
5
5
 
6
+ import { getCurrentTools } from '@earendil-works/pi-ai'
6
7
  import type { Context as PiContext, StreamOptions } from '@earendil-works/pi-ai'
7
8
  import type { ReplayEnvelope, StreamChunk } from '@deepseek-ai/dsh-llm'
8
9
  import { piLoginProviderByRoute } from './catalog.ts'
@@ -391,10 +392,15 @@ export function filterPiContext(
391
392
  api: string | undefined,
392
393
  policy: NativeToolPolicy = DEFAULT_NATIVE_TOOL_POLICY,
393
394
  ): PiContext {
394
- if (nativePlan(providerId, api, policy) === undefined || context.tools === undefined) return context
395
+ if (nativePlan(providerId, api, policy) === undefined) return context
395
396
  return {
396
397
  ...context,
397
- tools: context.tools.filter(tool => !isDshWebToolName(tool.name)),
398
+ ...(context.tools === undefined ? {} : { tools: context.tools.filter(tool => !isDshWebToolName(tool.name)) }),
399
+ messages: context.messages.map(message => message.role !== 'system' ? message : {
400
+ ...message,
401
+ ...(message.toolsAdded === undefined ? {} : { toolsAdded: message.toolsAdded.filter(tool => !isDshWebToolName(tool.name)) }),
402
+ ...(message.toolsRemoved === undefined ? {} : { toolsRemoved: message.toolsRemoved.filter(tool => !isDshWebToolName(tool.name)) }),
403
+ }),
398
404
  }
399
405
  }
400
406
 
@@ -413,7 +419,7 @@ export function prepareNativeToolRequest<TOptions extends StreamOptions>(
413
419
  api: string | undefined,
414
420
  policy: NativeToolPolicy = DEFAULT_NATIVE_TOOL_POLICY,
415
421
  ): { context: PiContext; options: TOptions & StreamOptions } {
416
- if (context.tools === undefined || nativePlan(providerId, api, policy) === undefined) {
422
+ if ((context.tools === undefined && getCurrentTools(context.messages).length === 0) || nativePlan(providerId, api, policy) === undefined) {
417
423
  return { context, options }
418
424
  }
419
425
  const onPayload = wrapOnPayload(options?.onPayload, providerId, api, policy)
package/src/provider.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /** Installed pi-ai providers remapped onto independent harness routes. */
2
2
 
3
+ import { normalizeContext } from '@earendil-works/pi-ai'
3
4
  import { builtinProviders } from '@earendil-works/pi-ai/providers/all'
4
5
  import type { Api, ApiKeyAuth, Model, Provider } from '@earendil-works/pi-ai'
5
6
  import { PI_LOGIN_PROVIDERS } from './catalog.ts'
@@ -108,7 +109,7 @@ export function harnessProvider(
108
109
  ? prepareOpenRouterOptions(model, request.options, catalog) : request.options
109
110
  return base.stream(
110
111
  model,
111
- request.context,
112
+ normalizeContext(request.context),
112
113
  // pi-ai's generic ApiStreamOptions<T> is a conditional type. The
113
114
  // preparation step preserves every provider-specific field and only
114
115
  // adds StreamOptions.onPayload, but TypeScript cannot prove that for T.
@@ -121,7 +122,7 @@ export function harnessProvider(
121
122
  ? prepareOpenRouterOptions(model, request.options, catalog) : request.options
122
123
  return base.streamSimple(
123
124
  model,
124
- request.context,
125
+ normalizeContext(request.context),
125
126
  guarded,
126
127
  )
127
128
  },