@goodandready/dsh-subscriptions 0.7.20 → 0.7.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.
@@ -68,6 +68,7 @@ export function createAdapterManager({
68
68
  recordHistory,
69
69
  fetchImpl: fetch,
70
70
  fetchForRef,
71
+ attachments: () => (typeof ctx.get === 'function' ? ctx.get('attachments') : null),
71
72
  hideDeprecatedModels: () => Boolean(live().hideDeprecatedModels),
72
73
  cascadingFallback: (...args) => cascadeFallbackStream(...args),
73
74
  ollamaFallback: typeof ollamaFallback === 'function' ? ollamaFallback : async function* ({ err }) { yield { type: 'finish', reason: { kind: 'error', failure: { code: err?.code || 'RATE_LIMIT', message: err?.message || 'all subscription accounts exhausted' } } } },
package/lib/adapter.js CHANGED
@@ -13,6 +13,7 @@ import { pickFetch } from './proxy.js'
13
13
  import { quotaSnapshot, parseBody } from './ratelimit.js'
14
14
  import { toTokenUsage } from './wire.js'
15
15
  import { isSwitchableError } from './rotate.js'
16
+ import { hydrateAttachmentImages } from './images.js'
16
17
 
17
18
  // #94: isDeprecatedId imported from ./models-catalog.js
18
19
 
@@ -156,8 +157,16 @@ export class SubscriptionAdapter extends LlmAdapter {
156
157
  const sessionId = options.sessionId || options.session_id || options.conversationId
157
158
  const effectiveReasoning = resolveEffectiveReasoning(sessionId, options)
158
159
  const decisionTrace = {}
159
- const streamOptions = {
160
+ const hydratedOptions = {
160
161
  ...options,
162
+ messages: await hydrateAttachmentImages(
163
+ options.messages,
164
+ typeof deps.attachments === 'function' ? deps.attachments() : null,
165
+ options.signal,
166
+ ),
167
+ }
168
+ const streamOptions = {
169
+ ...hydratedOptions,
161
170
  ...(provider === 'codex' && getSpeedMode(sessionId) === 'fast' ? { service_tier: 'priority' } : {}),
162
171
  ...(effectiveReasoning.effort ? { reasoningEffort: effectiveReasoning.effort } : {}),
163
172
  ...(effectiveReasoning.budgetTokens ? { thinkingBudget: effectiveReasoning.budgetTokens } : {}),
package/lib/images.js CHANGED
@@ -174,6 +174,38 @@ export function normalizeImage(item) {
174
174
  return null
175
175
  }
176
176
 
177
+ export async function hydrateAttachmentImages(messages, attachments, signal) {
178
+ if (!attachments || typeof attachments.readImageRequest !== 'function' || !Array.isArray(messages)) return messages
179
+ return Promise.all(messages.map(async (message) => {
180
+ if (!message || !Array.isArray(message.content)) return message
181
+ const content = await Promise.all(message.content.map(async (block) => {
182
+ if (!block || block.type !== 'image' || !block.attachment) return block
183
+ try {
184
+ const ref = block.attachment
185
+ const version = await attachments.readImageRequest(ref, {
186
+ width: Number(ref.width) || 1568,
187
+ height: Number(ref.height) || 1568,
188
+ maxBytes: 5 * 1024 * 1024,
189
+ }, signal)
190
+ return {
191
+ type: 'image',
192
+ source: {
193
+ type: 'base64',
194
+ media_type: version.mediaType || ref.mediaType || 'image/jpeg',
195
+ data: Buffer.from(version.data).toString('base64'),
196
+ },
197
+ }
198
+ } catch {
199
+ return {
200
+ type: 'text',
201
+ text: '[image attachment unavailable: ' + String(block.attachment.attachmentId || block.attachment.name || 'unknown') + ']',
202
+ }
203
+ }
204
+ }))
205
+ return { ...message, content }
206
+ }))
207
+ }
208
+
177
209
  export function extractImages(content) {
178
210
  if (!Array.isArray(content)) {
179
211
  const norm = normalizeImage(content)
package/lib/messages.js CHANGED
@@ -343,9 +343,19 @@ export function anthropicPayload(options, extraSystem) {
343
343
  }
344
344
  if (message.role === "tool") {
345
345
  const toolUseId = String(message.toolCallId || message.tool_call_id || message.tool_use_id || message.id || "")
346
- const content = typeof message.content === "string"
346
+ const images = extractImages(message.content)
347
+ let content = typeof message.content === "string"
347
348
  ? message.content
348
349
  : (flattenText(message.content) || JSON.stringify(message.content || ""))
350
+ if (images.length) {
351
+ const blocks = []
352
+ if (content) blocks.push({ type: "text", text: content })
353
+ for (const img of images) {
354
+ const blk = toAnthropicImage(img)
355
+ if (blk) blocks.push(blk)
356
+ }
357
+ if (blocks.length) content = blocks
358
+ }
349
359
  messages.push({
350
360
  role: "user",
351
361
  content: [{
@@ -73,10 +73,10 @@ export function defaults() {
73
73
  redirectUri: 'https://console.anthropic.com/oauth/code/callback',
74
74
  claudeCcVersion: '2.1.289.7e2',
75
75
  models: [
76
- { id: 'claude-opus-5', name: 'Claude Opus 5' },
77
- { id: 'claude-sonnet-5', name: 'Claude Sonnet 5' },
78
- { id: 'claude-fable-5', name: 'Claude Fable 5' },
79
- { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5' },
76
+ { id: 'claude-opus-5', name: 'Claude Opus 5', inputModalities: ['text', 'image'] },
77
+ { id: 'claude-sonnet-5', name: 'Claude Sonnet 5', inputModalities: ['text', 'image'] },
78
+ { id: 'claude-fable-5', name: 'Claude Fable 5', inputModalities: ['text', 'image'] },
79
+ { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5', inputModalities: ['text', 'image'] },
80
80
  ],
81
81
  systemPrefix: IDENTITY,
82
82
  }
@@ -189,9 +189,10 @@ export async function listModels(blob, cfg, fetchImpl) {
189
189
  const rawDefault = (cfg && cfg.models && cfg.models.length) ? cfg.models : defaults().models
190
190
  const defaultCatalog = modelCatalog(id, rawDefault.map((entry) => {
191
191
  const model = typeof entry === "string" ? { id: entry, name: entry } : { ...entry }
192
- const levels = effortLevelsForModel(model.id || model.name)
193
- if (!levels) return model
194
- return { ...model, reasoning: { efforts: levels.map((level) => ({ id: level, name: level })) } }
192
+ const withVision = { ...model, inputModalities: model.inputModalities || ['text', 'image'] }
193
+ const levels = effortLevelsForModel(withVision.id || withVision.name)
194
+ if (!levels) return withVision
195
+ return { ...withVision, reasoning: { efforts: levels.map((level) => ({ id: level, name: level })) } }
195
196
  }))
196
197
 
197
198
  if (!blob || !blob.accessToken) return defaultCatalog
@@ -223,6 +224,7 @@ export async function listModels(blob, cfg, fetchImpl) {
223
224
  id: modelId,
224
225
  name: item.display_name || modelId,
225
226
  ...(levels ? { reasoning: { efforts: levels.map((level) => ({ id: level, name: level })) } } : {}),
227
+ inputModalities: ['text', 'image'],
226
228
  })
227
229
  }
228
230
  if (!rows.length) return defaultCatalog
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.7.20",
3
+ "version": "0.7.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",