@dsh-plus/llm-pi 0.1.13 → 0.1.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plus/llm-pi",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "dsh-plus service+ui plugin: 基于 PiAiAdapter 的自定义 LLM 路由(全量 compat、模型继承、models.dev 目录兜底)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -23,9 +23,8 @@
23
23
  "dsh": {
24
24
  "client": {
25
25
  "inject": [
26
- "@deepseek-ai/dsh-client-runtime",
27
26
  "@deepseek-ai/dsh-client-locale",
28
- "@deepseek-ai/dsh-client-connection",
27
+ "@deepseek-ai/dsh-client-ui-renderer",
29
28
  "@deepseek-ai/dsh-api-remotes"
30
29
  ],
31
30
  "platform": "web"
@@ -33,35 +32,40 @@
33
32
  },
34
33
  "dependencies": {
35
34
  "https-proxy-agent": "^7.0.6",
36
- "@dsh-plus/shared": "0.1.5"
35
+ "@dsh-plus/shared": "0.1.6"
37
36
  },
38
37
  "peerDependencies": {
39
38
  "@deepseek-ai/cordis": "^4.0.1",
40
- "@deepseek-ai/dsh-anonymous-user-id": "^0.1.1-rc.2",
41
- "@deepseek-ai/dsh-credentials": "^0.1.0-rc.8",
42
- "@deepseek-ai/dsh-home-paths": "^0.1.0-rc.8",
43
- "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.8",
44
- "@deepseek-ai/dsh-launch-environment": "^0.1.0-rc.8",
45
- "@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
46
- "@deepseek-ai/dsh-llm-deepseek": "^0.1.1-rc.2",
47
- "@deepseek-ai/dsh-llm-pi-ai": "^0.1.0-rc.8",
48
- "@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
39
+ "@deepseek-ai/dsh-anonymous-user-id": "^0.1.2-alpha.1",
40
+ "@deepseek-ai/dsh-credentials": "^0.1.2-alpha.1",
41
+ "@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.1",
42
+ "@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.1",
43
+ "@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.1",
44
+ "@deepseek-ai/dsh-llm": "^0.1.2-alpha.1",
45
+ "@deepseek-ai/dsh-llm-deepseek": "^0.1.2-alpha.1",
46
+ "@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.1",
47
+ "@deepseek-ai/dsh-settings": "^0.1.2-alpha.1",
49
48
  "@deepseek-ai/schemastery": "^3.18.1",
50
- "@earendil-works/pi-ai": "^0.82.1"
49
+ "@earendil-works/pi-ai": "^0.84.2"
51
50
  },
52
51
  "devDependencies": {
53
52
  "@deepseek-ai/cordis": "4.0.1",
54
- "@deepseek-ai/dsh-anonymous-user-id": "0.1.1-rc.2",
55
- "@deepseek-ai/dsh-credentials": "0.1.1-rc.2",
56
- "@deepseek-ai/dsh-home-paths": "0.1.1-rc.2",
57
- "@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
58
- "@deepseek-ai/dsh-launch-environment": "0.1.1-rc.2",
59
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
60
- "@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
61
- "@deepseek-ai/dsh-llm-pi-ai": "0.1.1-rc.2",
62
- "@deepseek-ai/dsh-settings": "0.1.1-rc.2",
53
+ "@deepseek-ai/dsh-anonymous-user-id": "0.1.2-alpha.1",
54
+ "@deepseek-ai/dsh-atomic-write": "0.1.2-alpha.1",
55
+ "@deepseek-ai/dsh-attachment": "0.1.2-alpha.1",
56
+ "@deepseek-ai/dsh-credentials": "0.1.2-alpha.1",
57
+ "@deepseek-ai/dsh-home-paths": "0.1.2-alpha.1",
58
+ "@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.1",
59
+ "@deepseek-ai/dsh-launch-environment": "0.1.2-alpha.1",
60
+ "@deepseek-ai/dsh-llm": "0.1.2-alpha.1",
61
+ "@deepseek-ai/dsh-llm-deepseek": "0.1.2-alpha.1",
62
+ "@deepseek-ai/dsh-llm-pi-ai": "0.1.2-alpha.1",
63
+ "@deepseek-ai/dsh-settings": "0.1.2-alpha.1",
64
+ "@deepseek-ai/dsh-timeout": "0.1.2-alpha.1",
65
+ "@deepseek-ai/dsh-typert-protocol": "0.1.2-alpha.1",
66
+ "@deepseek-ai/dsh-util-crypto": "0.1.2-alpha.1",
63
67
  "@deepseek-ai/schemastery": "3.18.1",
64
- "@earendil-works/pi-ai": "0.82.1",
68
+ "@earendil-works/pi-ai": "0.84.2",
65
69
  "@types/react": "~18.3.1",
66
70
  "react": "^18.2.0"
67
71
  },
@@ -0,0 +1,177 @@
1
+ /**
2
+ * PiAiAdapter 认证注入的内联实现(官方 llm-pi-ai/src/auth.ts 的等价移植)。
3
+ *
4
+ * 官方 credentialStoreFrom/authContextFrom 仅在 @deepseek-ai/dsh-llm-pi-ai 的
5
+ * src/auth.ts 子路径导出(包根只导出 recordKeyFor),而 npm 发布形态不携带
6
+ * src/(lib/index.js 单 bundle)——dsh 树 dev 布局优先经 src 子路径走官方
7
+ * 实现(见 resolve-dsh.ts 探测),本文件在其余形态(npm 布局的 dsh 树 /
8
+ * vendored 兜底)下按官方语义逐行等价实现,并经 assertKitShape 自检兜底。
9
+ * recordKeyFor 由调用方注入(与 PiAiAdapter 同源模块的包根导出,保证记录
10
+ * 键格式与官方写入方一致)。
11
+ * @module llm-pi/auth-inline
12
+ */
13
+ import { access } from 'node:fs/promises'
14
+ import { homedir } from 'node:os'
15
+ import { resolve as resolvePath } from 'node:path'
16
+
17
+ import type { Context } from '@deepseek-ai/cordis'
18
+ import type {
19
+ CredentialKey,
20
+ CredentialProvider,
21
+ CredentialRecord,
22
+ } from '@deepseek-ai/dsh-credentials'
23
+ import {
24
+ credentialKeyId,
25
+ credentialKeyScope,
26
+ credentialRef,
27
+ isCredentialKeySegment,
28
+ isCredentialRefName,
29
+ } from '@deepseek-ai/dsh-credentials'
30
+ import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
31
+ import { LlmError } from '@deepseek-ai/dsh-llm'
32
+ import type {
33
+ AuthContext,
34
+ Credential,
35
+ CredentialInfo,
36
+ CredentialStore,
37
+ } from '@earendil-works/pi-ai'
38
+
39
+ /** 官方 RECORD_SCOPE 常量(llm-pi-ai/src/auth.ts)——读写作用域必须与官方一致。 */
40
+ export const INLINE_RECORD_SCOPE = 'llm-pi-ai'
41
+
42
+ /** 官方 jsonImage:pi-ai 凭据里显式 undefined 成员 JSON 序列化为缺省/数组 null。 */
43
+ function jsonImage(value: unknown): unknown {
44
+ if (Array.isArray(value))
45
+ return value.map((entry) => (entry === undefined ? null : jsonImage(entry)))
46
+ if (
47
+ typeof value === 'object' &&
48
+ value !== null &&
49
+ Object.getPrototypeOf(value) === Object.prototype
50
+ ) {
51
+ const image: Record<string, unknown> = {}
52
+ for (const [key, member] of Object.entries(value)) {
53
+ if (member !== undefined) image[key] = jsonImage(member)
54
+ }
55
+ return image
56
+ }
57
+ return value
58
+ }
59
+
60
+ /** 官方 toPiCredential:api-key 记录按字段重建;grant 载荷原样透传。 */
61
+ function toPiCredential(record: CredentialRecord | undefined): Credential | undefined {
62
+ if (record === undefined) return undefined
63
+ if (record.kind === 'api-key') {
64
+ return {
65
+ type: 'api_key',
66
+ ...(record.key === undefined ? {} : { key: record.key }),
67
+ ...(record.env === undefined ? {} : { env: { ...record.env } }),
68
+ }
69
+ }
70
+ return record.payload as Credential
71
+ }
72
+
73
+ /** 官方 toRecord:pi-ai 凭据 → 可存储的记录联合。 */
74
+ function toRecord(credential: Credential): CredentialRecord {
75
+ if (credential.type === 'api_key') {
76
+ return {
77
+ kind: 'api-key',
78
+ ...(credential.key === undefined ? {} : { key: credential.key }),
79
+ ...(credential.env === undefined ? {} : { env: { ...credential.env } }),
80
+ }
81
+ }
82
+ return { kind: 'grant', payload: jsonImage(credential) }
83
+ }
84
+
85
+ /** 官方 writableStore:写路径需要凭据服务,缺失时抛 NO_CREDENTIAL_STORE。 */
86
+ function writableStore(ctx: Context): CredentialProvider {
87
+ const credentials = ctx.get('credentials')
88
+ if (credentials === undefined) {
89
+ throw new LlmError(
90
+ 'llm-pi: 当前组合未挂载凭据服务(dsh-credentials-local),登录产生的凭据无处存储',
91
+ 'NO_CREDENTIAL_STORE',
92
+ )
93
+ }
94
+ return credentials
95
+ }
96
+
97
+ /**
98
+ * pi-ai `CredentialStore`(官方 credentialStoreFrom 等价实现)。
99
+ * 路由键任意而记录 id 受语法约束:语法外的 id 读答"未存储"、删除无事可做、
100
+ * modify 拒绝(写不落地不能报成功)。
101
+ */
102
+ export function credentialStoreFrom(
103
+ ctx: Context,
104
+ recordKeyFor: (providerId: string) => CredentialKey,
105
+ ): CredentialStore {
106
+ return {
107
+ async read(providerId) {
108
+ const credentials = ctx.get('credentials')
109
+ if (credentials === undefined) return undefined
110
+ if (!isCredentialKeySegment(providerId)) return undefined
111
+ return toPiCredential(await credentials.readRecord(recordKeyFor(providerId)))
112
+ },
113
+ async list(): Promise<readonly CredentialInfo[]> {
114
+ const stored = (await ctx.get('credentials')?.listRecords()) ?? []
115
+ const mine: CredentialInfo[] = []
116
+ for (const entry of stored) {
117
+ if (credentialKeyScope(entry.key) !== INLINE_RECORD_SCOPE) continue
118
+ mine.push({
119
+ providerId: credentialKeyId(entry.key),
120
+ type: entry.kind === 'api-key' ? 'api_key' : 'oauth',
121
+ })
122
+ }
123
+ return mine
124
+ },
125
+ async modify(providerId, mutate) {
126
+ if (!isCredentialKeySegment(providerId)) {
127
+ throw new LlmError(
128
+ `llm-pi: provider id "${providerId}" 无法寻址存储的凭据记录(记录 id 是小写连字符标识);` +
129
+ '该 route 请改用 apiKeyEnv 认证',
130
+ 'UNSTORABLE_PROVIDER_ID',
131
+ )
132
+ }
133
+ const stored = await writableStore(ctx).modifyRecord(
134
+ recordKeyFor(providerId),
135
+ async (current) => {
136
+ const next = await mutate(toPiCredential(current))
137
+ return next === undefined ? undefined : toRecord(next)
138
+ },
139
+ )
140
+ return toPiCredential(stored)
141
+ },
142
+ async delete(providerId) {
143
+ if (!isCredentialKeySegment(providerId)) return
144
+ await writableStore(ctx).deleteRecord(recordKeyFor(providerId))
145
+ },
146
+ }
147
+ }
148
+
149
+ /**
150
+ * pi-ai `AuthContext`(官方 authContextFrom 等价实现):
151
+ * env() 先查凭据服务(存储的环境值对 provider 自发现可见),再回退启动环境;
152
+ * fileExists() 问宿主进程文件系统(~/.aws/credentials 等是本进程所在机器的事实)。
153
+ */
154
+ export function authContextFrom(ctx: Context): AuthContext {
155
+ return {
156
+ async env(name) {
157
+ if (isCredentialRefName(name)) {
158
+ const credentials = ctx.get('credentials')
159
+ const hit = await credentials?.resolve(credentialRef(name))
160
+ if (hit !== undefined) return hit.value
161
+ }
162
+ return launchEnvironmentOf(ctx).get(name)?.value
163
+ },
164
+ async fileExists(path) {
165
+ const expanded =
166
+ path.startsWith('~/') || path === '~'
167
+ ? resolvePath(homedir(), path.slice(1).replace(/^\//, ''))
168
+ : path
169
+ try {
170
+ await access(expanded)
171
+ return true
172
+ } catch {
173
+ return false
174
+ }
175
+ },
176
+ }
177
+ }
@@ -17,7 +17,6 @@ export interface OfficialModelBase {
17
17
  input?: ('text' | 'image')[]
18
18
  imagePixelBudget?: number
19
19
  imageMaxBytes?: number
20
- imageDetail?: 'auto' | 'low'
21
20
  }
22
21
 
23
22
  interface OfficialCatalog {
@@ -46,7 +45,6 @@ function catalogOf(kit: DshKit): OfficialCatalog {
46
45
  : { input: [...model.inputModalities] as ('text' | 'image')[] }),
47
46
  ...(model.imagePixelBudget === undefined ? {} : { imagePixelBudget: model.imagePixelBudget }),
48
47
  ...(model.imageMaxBytes === undefined ? {} : { imageMaxBytes: model.imageMaxBytes }),
49
- ...(model.imageDetail === undefined ? {} : { imageDetail: model.imageDetail }),
50
48
  })
51
49
  }
52
50
  const catalog: OfficialCatalog = { baseUrl: resolved.baseURL, models }
package/src/client/api.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  /**
2
- * 自定义端点通道:仅剩「模型目录」(配置读写已迁移到官方 settingsScope
3
- * 传输,见 scope.ts card.tsx)。目录响应附带 kitSource models-dev
4
- * 状态,供卡片的状态行展示(运行期诊断,非配置数据)。
2
+ * 自定义端点通道:仅剩「模型目录」(配置读写已迁移到 ctx.remote.settings
3
+ * 直连传输,0.1.2-alpha.1 起 connection.api.settings 已移除;见 scope.ts
4
+ * card.tsx)。目录响应附带 kitSource 与 models-dev 状态,供卡片的状态行
5
+ * 展示(运行期诊断,非配置数据)。
5
6
  * @module llm-pi/client/api
6
7
  */
7
8
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 「LLM 路由」配置卡片:注册进 settings.plugin.item 插槽(官方插件配置页)。
3
3
  * 顶部:enabled / catalogUrl / catalogRefreshHours / 只读状态行(kitSource、
4
- * modelsDevStatus,来自模型目录端点)+ 保存(settings.replace 全量)与
4
+ * modelsDevStatus,来自模型目录端点)+ 保存(settings.update 全量深合并)与
5
5
  * 错误/成功提示;下方为 providers 路由列表(新增/删除/字段编辑/compat/模型
6
6
  * 目录,见 views/)。外壳与基础控件走 @dsh-plus/shared/client 套件。
7
7
  * @module llm-pi/client/card
@@ -12,12 +12,11 @@ import {
12
12
  type CardStatusState,
13
13
  CheckRow,
14
14
  IDLE_STATUS,
15
+ type NamespaceSettingsApi,
15
16
  type Scope,
16
- type SettingsApi,
17
17
  TextField,
18
18
  } from '@dsh-plus/shared/client'
19
19
  import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
20
- import { SETTINGS_NS } from '../ns.ts'
21
20
  import { type ConfigValue, fetchCatalog, refreshCatalog, type WireModelsDevStatus } from './api.ts'
22
21
  import {
23
22
  type Draft,
@@ -32,7 +31,7 @@ import { ProvidersSection } from './views/providers.tsx'
32
31
  export interface CardProps {
33
32
  t(key: string): string
34
33
  scope: Scope
35
- api: SettingsApi
34
+ api: NamespaceSettingsApi
36
35
  }
37
36
 
38
37
  function modelsDevText(status: WireModelsDevStatus | null, t: (key: string) => string): string {
@@ -129,15 +128,8 @@ export function LlmPiCard(props: CardProps): ReactElement | null {
129
128
  setSaving(true)
130
129
  const revision = scope.getSnapshot().revision
131
130
  api
132
- .replace({
133
- ns: SETTINGS_NS,
134
- section: toPatch(draft) as unknown as Record<string, unknown>,
135
- ...(revision !== undefined ? { expectedRevision: revision } : {}),
136
- })
137
- .then(async (response) => {
138
- if (!response.result.ok) {
139
- throw new Error(response.result.error?.message ?? t('saveFailed'))
140
- }
131
+ .update(toPatch(draft) as unknown as Record<string, unknown>, revision)
132
+ .then(async () => {
141
133
  await scope.load()
142
134
  const next = scope.getSnapshot().value as ConfigValue | undefined
143
135
  if (next !== undefined) setDraft(draftFromValue(next))
@@ -3,21 +3,23 @@
3
3
  * 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
4
4
  * (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
5
5
  *
6
- * 类型说明:浏览器半只用到 slots/locale/connection/remote 的很窄一面,
7
- * scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现);
8
- * 运行时契约以官方 dsh-client-ui-settings-plugins
9
- * settings.plugin.item 插槽与 dsh-client-connection 的 settings RPC 面为准。
10
- * rc7 起该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
6
+ * 类型说明:浏览器半只用到 slots/locale/remote 的很窄一面,
7
+ * scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
8
+ * 该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
11
9
  * (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
12
10
  * 官方配置页按此 key 与 Host 已注册命名空间配对分发。
13
- * 配置读写经官方 settings RPC 直连(rc7 起第三方命名空间全量开放;
14
- * 不复用 settingsScope 服务——非 loopback 页面下它无数据,见 scope.ts);
15
- * 自定义端点仅剩「模型目录」(api.ts,含 kitSource/models-dev
16
- * 运行期诊断)。
11
+ * 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
12
+ * 已移除;不复用 settingsScope 服务——非 loopback 页面下它固定 memory 模式无数据,
13
+ * 见 @dsh-plus/shared/client scope.ts);自定义端点仅剩「模型目录」
14
+ * (api.ts,含 kitSource/models-dev 运行期诊断)。
17
15
  * @module @dsh-plus/llm-pi/client
18
16
  */
19
17
  import type { Context } from '@deepseek-ai/cordis'
20
- import { createApiScope } from '@dsh-plus/shared/client'
18
+ import {
19
+ createNamespaceApi,
20
+ createSettingsScope,
21
+ type SettingsRemoteFace,
22
+ } from '@dsh-plus/shared/client'
21
23
  import { SETTINGS_NS } from '../ns.ts'
22
24
  import { LlmPiCard } from './card.tsx'
23
25
  import { en, NS, zh } from './i18n.ts'
@@ -26,7 +28,7 @@ import { injectStyle } from './styles.ts'
26
28
  export const name = 'dsh-plus-llm-pi'
27
29
 
28
30
  /** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
29
- export const inject = ['slots', 'locale', 'connection', 'remote'] as const
31
+ export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
30
32
 
31
33
  interface SlotsLike {
32
34
  inject(key: string, callback: () => unknown): unknown
@@ -39,17 +41,13 @@ interface LocaleLike {
39
41
  }
40
42
 
41
43
  interface RemoteLike {
44
+ settings: SettingsRemoteFace
42
45
  $on(event: string, listener: (payload?: unknown) => void): () => void
43
46
  }
44
47
 
45
- interface ConnectionLike {
46
- api: { settings: Parameters<typeof createApiScope>[0] }
47
- }
48
-
49
48
  interface ClientContext {
50
49
  slots: SlotsLike
51
50
  locale: LocaleLike
52
- get(key: 'connection'): ConnectionLike
53
51
  get(key: 'remote'): RemoteLike
54
52
  on(event: string, listener: () => void): () => void
55
53
  effect(execute: () => () => void, label?: string): unknown
@@ -65,8 +63,8 @@ export function apply(ctx: Context): void {
65
63
  'llm-pi: style',
66
64
  )
67
65
  c.effect(() => c.locale.register(NS, { zh, en }), 'llm-pi: locale')
68
- const api = c.get('connection').api.settings
69
- const scope = createApiScope(api, SETTINGS_NS, c, 'llm-pi: settings scope')
66
+ const scope = createSettingsScope(c, SETTINGS_NS, 'llm-pi: settings scope')
67
+ const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
70
68
  c.slots.inject('settings.plugin.item', () =>
71
69
  c.slots.register(
72
70
  {
@@ -29,13 +29,18 @@ export const BUDGET_KEYS = ['minimal', 'low', 'medium', 'high'] as const
29
29
 
30
30
  type CompatValue = 'boolean' | 'object' | readonly string[]
31
31
 
32
- /** 逐协议 compat 字段表(来源:compat.ts FIELDS_BY_PROTOCOL)。 */
32
+ /**
33
+ * 逐协议 compat 字段表(来源:compat.ts,逐字段镜像官方 catalog.ts COMPAT_GATES)。
34
+ * 只列 offer 字段(withhold 字段官方写时拒绝,UI 不提供);取值约束对齐官方
35
+ * config.ts compatProfile schema。
36
+ */
33
37
  const COMPAT_FIELDS: Record<string, Record<string, CompatValue>> = {
34
38
  'openai-completions': {
35
39
  supportsStore: 'boolean',
36
40
  supportsDeveloperRole: 'boolean',
37
41
  supportsReasoningEffort: 'boolean',
38
42
  supportsUsageInStreaming: 'boolean',
43
+ supportsFinishReason: 'boolean',
39
44
  maxTokensField: ['max_completion_tokens', 'max_tokens'],
40
45
  requiresToolResultName: 'boolean',
41
46
  requiresAssistantAfterToolResult: 'boolean',
@@ -43,9 +48,10 @@ const COMPAT_FIELDS: Record<string, Record<string, CompatValue>> = {
43
48
  requiresReasoningContentOnAssistantMessages: 'boolean',
44
49
  thinkingFormat: [
45
50
  'openai',
46
- 'openrouter',
47
51
  'deepseek',
52
+ 'openrouter',
48
53
  'together',
54
+ 'baseten',
49
55
  'zai',
50
56
  'qwen',
51
57
  'chat-template',
@@ -54,36 +60,25 @@ const COMPAT_FIELDS: Record<string, Record<string, CompatValue>> = {
54
60
  'ant-ling',
55
61
  ],
56
62
  chatTemplateKwargs: 'object',
57
- openRouterRouting: 'object',
58
- vercelGatewayRouting: 'object',
59
- zaiToolStream: 'boolean',
60
- supportsOpenAIGrammarTools: 'boolean',
63
+ chatTemplateArgs: 'object',
64
+ supportsThinkingTokenBudget: 'boolean',
61
65
  supportsStrictMode: 'boolean',
62
66
  cacheControlFormat: ['anthropic'],
63
- sendSessionAffinityHeaders: 'boolean',
64
- deferredToolsMode: ['kimi'],
65
- sessionAffinityFormat: ['openai', 'openai-nosession', 'openrouter'],
66
67
  supportsLongCacheRetention: 'boolean',
67
68
  },
68
69
  'openai-responses': {
69
70
  supportsDeveloperRole: 'boolean',
70
- sessionAffinityFormat: ['openai', 'openai-nosession', 'openrouter'],
71
- supportsLongCacheRetention: 'boolean',
72
71
  supportsStrictMode: 'boolean',
73
- supportsOpenAIGrammarTools: 'boolean',
74
- supportsToolSearch: 'boolean',
75
- supportsExplicitPromptCacheMode: 'boolean',
72
+ supportsLongCacheRetention: 'boolean',
76
73
  },
77
74
  'anthropic-messages': {
78
75
  supportsEagerToolInputStreaming: 'boolean',
79
76
  supportsLongCacheRetention: 'boolean',
80
- sendSessionAffinityHeaders: 'boolean',
81
77
  supportsCacheControlOnTools: 'boolean',
82
78
  supportsTemperature: 'boolean',
83
79
  forceAdaptiveThinking: 'boolean',
84
80
  allowEmptySignature: 'boolean',
85
81
  supportsStrictTools: 'boolean',
86
- supportsToolReferences: 'boolean',
87
82
  },
88
83
  }
89
84