@goodandready/dsh-subscriptions 0.6.20 → 0.6.21

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/adapter.js CHANGED
@@ -43,15 +43,16 @@ export class SubscriptionAdapter extends LlmAdapter {
43
43
 
44
44
  async listModels(provider) {
45
45
  const accounts = await this.deps.listAccounts(provider)
46
- if (!accounts.some((a) => a.hasToken)) return []
46
+ const targetAccount = accounts.find((a) => a.hasToken)
47
+ if (!targetAccount) return []
47
48
  const cfg = this.deps.vendorConfig(provider)
48
- const fetchImpl = this.deps.fetchImpl || fetch
49
+ const fetchImpl = pickFetch(this.deps, targetAccount.ref)
49
50
  let models = null
50
51
  try {
51
52
  const blob = await this.deps.ensureFresh(
52
53
  provider,
53
- await this.deps.loadBlob(accounts.find((a) => a.hasToken).ref),
54
- accounts.find((a) => a.hasToken).ref,
54
+ await this.deps.loadBlob(targetAccount.ref),
55
+ targetAccount.ref,
55
56
  )
56
57
  const live = await getVendor(provider).listModels(blob, cfg, fetchImpl)
57
58
  if (Array.isArray(live) && live.length) models = live
package/lib/messages.js CHANGED
@@ -10,11 +10,13 @@ function flattenText(content) {
10
10
  }
11
11
 
12
12
  function toolResults(content) {
13
- return Array.isArray(content) ? content.filter((b) => b && b.type === 'tool-result') : []
13
+ if (!Array.isArray(content)) return []
14
+ return content.filter((b) => b && (b.type === 'tool-result' || b.type === 'tool_result' || b.type === 'function_call_output'))
14
15
  }
15
16
 
16
17
  function toolCalls(content) {
17
- return Array.isArray(content) ? content.filter((b) => b && b.type === 'tool-call') : []
18
+ if (!Array.isArray(content)) return []
19
+ return content.filter((b) => b && (b.type === 'tool-call' || b.type === 'tool_use' || b.type === 'function_call'))
18
20
  }
19
21
 
20
22
  export function openaiMessages(options) {
@@ -26,11 +28,21 @@ export function openaiMessages(options) {
26
28
  continue
27
29
  }
28
30
  if (message.role === 'assistant') {
29
- const calls = toolCalls(message.content).map((block) => ({
30
- id: block.id,
31
- type: 'function',
32
- function: { name: block.name, arguments: block.arguments || '{}' },
33
- }))
31
+ const calls = [
32
+ ...toolCalls(message.content).map((block) => ({
33
+ id: block.id,
34
+ type: 'function',
35
+ function: { name: block.name, arguments: block.arguments || '{}' },
36
+ })),
37
+ ...((message.tool_calls || message.toolCalls || []).map((c) => ({
38
+ id: c.id || c.call_id,
39
+ type: 'function',
40
+ function: {
41
+ name: c.name || (c.function && c.function.name),
42
+ arguments: typeof c.arguments === 'string' ? c.arguments : (c.function && c.function.arguments) || '{}',
43
+ },
44
+ }))),
45
+ ]
34
46
  out.push({
35
47
  role: 'assistant',
36
48
  content: flattenText(message.content) || (calls.length ? null : ''),
@@ -38,14 +50,26 @@ export function openaiMessages(options) {
38
50
  })
39
51
  continue
40
52
  }
53
+ if (message.role === 'tool') {
54
+ const toolCallId = String(message.toolCallId || message.tool_call_id || message.call_id || message.id || '')
55
+ const content = typeof message.content === 'string'
56
+ ? message.content
57
+ : (flattenText(message.content) || JSON.stringify(message.content || ''))
58
+ out.push({
59
+ role: 'tool',
60
+ tool_call_id: toolCallId,
61
+ content: content || '(no output)',
62
+ })
63
+ continue
64
+ }
41
65
  const results = toolResults(message.content)
42
66
  const text = flattenText(message.content)
43
67
  if (text || !results.length) out.push({ role: 'user', content: text })
44
68
  for (const result of results) {
45
69
  out.push({
46
70
  role: 'tool',
47
- tool_call_id: result.toolCallId,
48
- content: flattenText(result.content) || '(no output)',
71
+ tool_call_id: String(result.toolCallId || result.tool_call_id || result.call_id || result.id || ''),
72
+ content: typeof result.content === 'string' ? result.content : (flattenText(result.content) || '(no output)'),
49
73
  })
50
74
  }
51
75
  }
@@ -65,16 +89,74 @@ export function openaiTools(options) {
65
89
  }))
66
90
  }
67
91
 
92
+ export function reconcileResponsesInput(items) {
93
+ const result = []
94
+ const callsSeen = new Set()
95
+ const outputsSeen = new Set()
96
+
97
+ for (const it of items) {
98
+ if (it && it.type === 'function_call_output' && it.call_id) {
99
+ outputsSeen.add(it.call_id)
100
+ }
101
+ }
102
+
103
+ const pendingCalls = []
104
+ for (const it of items) {
105
+ if (!it) continue
106
+ if (it.type === 'function_call') {
107
+ if (it.call_id) {
108
+ callsSeen.add(it.call_id)
109
+ if (!outputsSeen.has(it.call_id)) {
110
+ pendingCalls.push(it.call_id)
111
+ }
112
+ }
113
+ result.push(it)
114
+ continue
115
+ }
116
+ if (it.type === 'function_call_output') {
117
+ // Drop orphan output if no matching preceding call
118
+ if (it.call_id && callsSeen.has(it.call_id)) {
119
+ result.push(it)
120
+ }
121
+ continue
122
+ }
123
+ // Flush synthetic outputs for any calls that did not get output before user/assistant turn
124
+ while (pendingCalls.length) {
125
+ const orphanId = pendingCalls.shift()
126
+ result.push({
127
+ type: 'function_call_output',
128
+ call_id: orphanId,
129
+ output: '{"status":"interrupted"}',
130
+ })
131
+ }
132
+ result.push(it)
133
+ }
134
+
135
+ while (pendingCalls.length) {
136
+ const orphanId = pendingCalls.shift()
137
+ result.push({
138
+ type: 'function_call_output',
139
+ call_id: orphanId,
140
+ output: '{"status":"interrupted"}',
141
+ })
142
+ }
143
+
144
+ if (!result.length) {
145
+ result.push({ role: 'user', content: [{ type: 'input_text', text: '' }] })
146
+ }
147
+ return result
148
+ }
149
+
68
150
  export function codexResponsesBody(options, fallbackInstructions, vendorCfg) {
69
151
  const systemParts = []
70
152
  if (options.system) systemParts.push(options.system)
71
- const input = []
153
+ const rawInput = []
72
154
  let pendingRole = null
73
155
  let pendingContent = []
74
156
  function flush() {
75
157
  if (!pendingRole) return
76
158
  const type = pendingRole === 'assistant' ? 'output_text' : 'input_text'
77
- input.push({
159
+ rawInput.push({
78
160
  role: pendingRole,
79
161
  content: pendingContent.length ? pendingContent : [{ type, text: '' }],
80
162
  })
@@ -95,30 +177,61 @@ export function codexResponsesBody(options, fallbackInstructions, vendorCfg) {
95
177
  if (message.role === 'assistant') {
96
178
  const text = flattenText(message.content)
97
179
  if (text) addText('assistant', text)
98
- for (const call of toolCalls(message.content)) {
180
+ const calls = [
181
+ ...toolCalls(message.content),
182
+ ...((message.tool_calls || message.toolCalls || []).map((c) => ({
183
+ id: c.id || c.call_id,
184
+ name: c.name || (c.function && c.function.name),
185
+ arguments: typeof c.arguments === 'string' ? c.arguments : (c.function && c.function.arguments) || c.arguments || '{}',
186
+ }))),
187
+ ]
188
+ for (const call of calls) {
189
+ if (!call.id) continue
99
190
  flush()
100
- input.push({
191
+ rawInput.push({
101
192
  type: 'function_call',
102
- call_id: String(call.id || ''),
193
+ call_id: String(call.id),
103
194
  name: call.name,
104
195
  arguments: typeof call.arguments === 'string' ? call.arguments : JSON.stringify(call.arguments || {}),
105
196
  })
106
197
  }
107
198
  continue
108
199
  }
200
+ if (message.role === 'tool') {
201
+ flush()
202
+ const callId = String(message.toolCallId || message.tool_call_id || message.call_id || message.id || '')
203
+ const outText = typeof message.content === 'string'
204
+ ? message.content
205
+ : (flattenText(message.content) || JSON.stringify(message.content || ''))
206
+ if (callId) {
207
+ rawInput.push({
208
+ type: 'function_call_output',
209
+ call_id: callId,
210
+ output: outText,
211
+ })
212
+ }
213
+ continue
214
+ }
215
+ const results = toolResults(message.content)
109
216
  const text = flattenText(message.content)
110
217
  if (text) addText('user', text)
111
- for (const result of toolResults(message.content)) {
218
+ for (const result of results) {
112
219
  flush()
113
- input.push({
114
- type: 'function_call_output',
115
- call_id: String(result.toolCallId || ''),
116
- output: flattenText(result.content) || '',
117
- })
220
+ const callId = String(result.toolCallId || result.tool_call_id || result.call_id || result.id || '')
221
+ const outText = typeof result.content === 'string'
222
+ ? result.content
223
+ : (flattenText(result.content) || (result.output != null ? (typeof result.output === 'string' ? result.output : JSON.stringify(result.output)) : ''))
224
+ if (callId) {
225
+ rawInput.push({
226
+ type: 'function_call_output',
227
+ call_id: callId,
228
+ output: outText,
229
+ })
230
+ }
118
231
  }
119
232
  }
120
233
  flush()
121
- if (!input.length) input.push({ role: 'user', content: [{ type: 'input_text', text: '' }] })
234
+ const input = reconcileResponsesInput(rawInput)
122
235
  const tools = openaiTools(options)
123
236
  const responsesTools = tools && tools.map((tool) => ({
124
237
  type: 'function',
@@ -160,7 +273,16 @@ export function anthropicPayload(options, extraSystem) {
160
273
  const blocks = []
161
274
  const text = flattenText(message.content)
162
275
  if (text) blocks.push({ type: 'text', text })
163
- for (const call of toolCalls(message.content)) {
276
+ const calls = [
277
+ ...toolCalls(message.content),
278
+ ...((message.tool_calls || message.toolCalls || []).map((c) => ({
279
+ id: c.id || c.call_id,
280
+ name: c.name || (c.function && c.function.name),
281
+ arguments: c.arguments || (c.function && c.function.arguments) || '{}',
282
+ }))),
283
+ ]
284
+ for (const call of calls) {
285
+ if (!call.id) continue
164
286
  blocks.push({
165
287
  type: 'tool_use',
166
288
  id: call.id,
@@ -171,14 +293,33 @@ export function anthropicPayload(options, extraSystem) {
171
293
  messages.push({ role: 'assistant', content: blocks.length ? blocks : [{ type: 'text', text: '' }] })
172
294
  continue
173
295
  }
296
+ if (message.role === 'tool') {
297
+ const toolUseId = String(message.toolCallId || message.tool_call_id || message.tool_use_id || message.id || '')
298
+ const content = typeof message.content === 'string'
299
+ ? message.content
300
+ : (flattenText(message.content) || JSON.stringify(message.content || ''))
301
+ messages.push({
302
+ role: 'user',
303
+ content: [{
304
+ type: 'tool_result',
305
+ tool_use_id: toolUseId,
306
+ content: content || '',
307
+ }],
308
+ })
309
+ continue
310
+ }
174
311
  const blocks = []
175
312
  const text = flattenText(message.content)
176
313
  if (text) blocks.push({ type: 'text', text })
177
314
  for (const result of toolResults(message.content)) {
315
+ const toolUseId = String(result.toolCallId || result.tool_call_id || result.tool_use_id || result.id || '')
316
+ const content = typeof result.content === 'string'
317
+ ? result.content
318
+ : (flattenText(result.content) || (result.output != null ? (typeof result.output === 'string' ? result.output : JSON.stringify(result.output)) : ''))
178
319
  blocks.push({
179
320
  type: 'tool_result',
180
- tool_use_id: result.toolCallId,
181
- content: flattenText(result.content) || '',
321
+ tool_use_id: toolUseId,
322
+ content: content || '',
182
323
  })
183
324
  }
184
325
  messages.push({ role: 'user', content: blocks.length ? blocks : [{ type: 'text', text: '' }] })
@@ -210,18 +351,45 @@ export function googleContents(options) {
210
351
  systemParts.push({ text: flattenText(message.content) })
211
352
  continue
212
353
  }
354
+ if (message.role === 'tool') {
355
+ const name = message.toolName || message.name || 'tool'
356
+ const output = typeof message.content === 'string'
357
+ ? message.content
358
+ : (flattenText(message.content) || JSON.stringify(message.content || ''))
359
+ contents.push({
360
+ role: 'user',
361
+ parts: [{
362
+ functionResponse: {
363
+ name,
364
+ response: { output },
365
+ },
366
+ }],
367
+ })
368
+ continue
369
+ }
213
370
  const role = message.role === 'assistant' ? 'model' : 'user'
214
371
  const parts = []
215
372
  const text = flattenText(message.content)
216
373
  if (text) parts.push({ text })
217
- for (const call of toolCalls(message.content)) {
374
+ const calls = [
375
+ ...toolCalls(message.content),
376
+ ...((message.tool_calls || message.toolCalls || []).map((c) => ({
377
+ name: c.name || (c.function && c.function.name),
378
+ arguments: c.arguments || (c.function && c.function.arguments) || '{}',
379
+ }))),
380
+ ]
381
+ for (const call of calls) {
218
382
  parts.push({ functionCall: { name: call.name, args: safeJson(call.arguments) } })
219
383
  }
220
384
  for (const result of toolResults(message.content)) {
385
+ const name = result.toolName || result.name || 'tool'
386
+ const output = typeof result.content === 'string'
387
+ ? result.content
388
+ : (flattenText(result.content) || (result.output != null ? (typeof result.output === 'string' ? result.output : JSON.stringify(result.output)) : ''))
221
389
  parts.push({
222
390
  functionResponse: {
223
- name: result.toolName || result.name || 'tool',
224
- response: { output: flattenText(result.content) },
391
+ name,
392
+ response: { output },
225
393
  },
226
394
  })
227
395
  }
@@ -242,24 +410,32 @@ export function googleContents(options) {
242
410
  }
243
411
 
244
412
  function safeJson(text) {
245
- if (text && typeof text === 'object') return text
246
- try { return JSON.parse(text || '{}') } catch { return {} }
413
+ if (typeof text === 'object' && text !== null) return text
414
+ try { return JSON.parse(text) } catch { return {} }
247
415
  }
248
416
 
249
- export function modelCatalog(provider, entries) {
250
- return (entries || []).map((entry) => {
251
- if (!entry) return null
252
- if (typeof entry === 'string') return { provider, id: entry, name: entry }
253
- const id = entry.id || entry.slug || entry.name
254
- if (!id) return null
255
- return {
256
- provider,
257
- id,
258
- name: entry.name || entry.display_name || id,
259
- ...(entry.description ? { description: entry.description } : {}),
260
- ...(entry.contextWindow ? { contextWindow: entry.contextWindow } : {}),
261
- ...(entry.inputModalities ? { inputModalities: entry.inputModalities } : {}),
262
- ...(entry.reasoning ? { reasoning: entry.reasoning } : {}),
417
+ export function modelCatalog(provider, list) {
418
+ if (!Array.isArray(list)) return []
419
+ const out = []
420
+ for (const entry of list) {
421
+ if (!entry) continue
422
+ if (typeof entry === 'string') {
423
+ out.push({ provider, id: entry, name: entry })
424
+ continue
263
425
  }
264
- }).filter(Boolean)
265
- }
426
+ if (typeof entry === 'object') {
427
+ const id = entry.id || entry.slug || entry.name
428
+ if (!id) continue
429
+ out.push({
430
+ provider,
431
+ id,
432
+ name: entry.name || entry.display_name || id,
433
+ ...(entry.description ? { description: entry.description } : {}),
434
+ ...(entry.contextWindow || entry.context_window ? { contextWindow: entry.contextWindow || entry.context_window } : {}),
435
+ ...(entry.reasoning != null ? { reasoning: entry.reasoning } : {}),
436
+ ...(entry.inputModalities || entry.input_modalities ? { inputModalities: entry.inputModalities || entry.input_modalities } : {}),
437
+ })
438
+ }
439
+ }
440
+ return out
441
+ }
@@ -30,6 +30,9 @@ export function defaults() {
30
30
  baseUrl: 'https://chatgpt.com/backend-api/codex',
31
31
  originator: 'codex_cli_rs',
32
32
  models: [
33
+ { id: 'gpt-5.6-luna', name: 'GPT-5.6 Luna' },
34
+ { id: 'gpt-5.3-codex', name: 'GPT-5.3 Codex' },
35
+ { id: 'gpt-5.2-codex', name: 'GPT-5.2 Codex' },
33
36
  { id: 'gpt-5.1-codex', name: 'GPT-5.1 Codex' },
34
37
  { id: 'gpt-5.1-codex-mini', name: 'GPT-5.1 Codex Mini' },
35
38
  { id: 'gpt-5.1', name: 'GPT-5.1' },
@@ -141,7 +144,8 @@ export async function refresh(cfg, blob, fetchImpl) {
141
144
  }
142
145
 
143
146
  export async function listModels(blob, cfg, fetchImpl) {
144
- const catalog = modelCatalog(id, cfg.models || defaults().models)
147
+ const rawModels = (cfg && cfg.models && cfg.models.length) ? cfg.models : defaults().models
148
+ const catalog = modelCatalog(id, rawModels)
145
149
  const impl = fetchImpl || fetch
146
150
  try {
147
151
  const base = (cfg.baseUrl || defaults().baseUrl).replace(/\/$/, '')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.6.20",
3
+ "version": "0.6.21",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",