@chatcode/cco-llm-chatcode-config 0.1.0
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/LICENSE +21 -0
- package/README.md +124 -0
- package/README.zh.md +133 -0
- package/cordis.patch.yml +4 -0
- package/cordis.web.patch.yml +12 -0
- package/docs/chatcode-login.md +88 -0
- package/docs/chatcode-login.zh.md +179 -0
- package/docs/chatcode-models.md +29 -0
- package/docs/chatcode-models.zh.md +29 -0
- package/docs/chatcode-reporting.md +96 -0
- package/docs/chatcode-reporting.zh.md +96 -0
- package/docs/decisions/2026-08-31-chatcode-model-source.md +39 -0
- package/docs/decisions/2026-08-31-chatcode-model-source.zh.md +39 -0
- package/docs/decisions/2026-09-16-actual-model-adapter-routing.md +31 -0
- package/docs/decisions/2026-09-16-actual-model-adapter-routing.zh.md +31 -0
- package/lib/client.js +469 -0
- package/lib/index.d.ts +263 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4873 -0
- package/lib/index.js.map +1 -0
- package/lib/startup-gate-BaCbWaKH.js +164 -0
- package/lib/startup-gate-BaCbWaKH.js.map +1 -0
- package/lib/web-startup.d.ts +9 -0
- package/lib/web-startup.d.ts.map +1 -0
- package/lib/web-startup.js +20 -0
- package/lib/web-startup.js.map +1 -0
- package/package.json +121 -0
- package/vendor/README.md +7 -0
- package/vendor/dsh-llm-pi-ai/LICENSE +21 -0
- package/vendor/dsh-llm-pi-ai/README.i18n.yaml +6 -0
- package/vendor/dsh-llm-pi-ai/README.md +238 -0
- package/vendor/dsh-llm-pi-ai/README.zh.md +238 -0
- package/vendor/dsh-llm-pi-ai/package.json +65 -0
- package/vendor/dsh-llm-pi-ai/src/adapter.ts +434 -0
- package/vendor/dsh-llm-pi-ai/src/auth.ts +241 -0
- package/vendor/dsh-llm-pi-ai/src/catalog.ts +908 -0
- package/vendor/dsh-llm-pi-ai/src/config.ts +478 -0
- package/vendor/dsh-llm-pi-ai/src/context.ts +349 -0
- package/vendor/dsh-llm-pi-ai/src/discovery.ts +284 -0
- package/vendor/dsh-llm-pi-ai/src/index.ts +336 -0
- package/vendor/dsh-llm-pi-ai/src/invariant.ts +30 -0
- package/vendor/dsh-llm-pi-ai/src/login.ts +161 -0
- package/vendor/dsh-llm-pi-ai/src/provider.ts +192 -0
- package/vendor/dsh-llm-pi-ai/src/replay.ts +249 -0
- package/vendor/dsh-llm-pi-ai/src/stream.ts +232 -0
- package/vendor/dsh-llm-pi-ai/tests/adapter.e2e.ts +168 -0
- package/vendor/dsh-llm-pi-ai/tests/adapter.spec.ts +1034 -0
- package/vendor/dsh-llm-pi-ai/tests/assemble.ts +32 -0
- package/vendor/dsh-llm-pi-ai/tests/auth-double.ts +39 -0
- package/vendor/dsh-llm-pi-ai/tests/auth.spec.ts +221 -0
- package/vendor/dsh-llm-pi-ai/tests/catalog.spec.ts +1220 -0
- package/vendor/dsh-llm-pi-ai/tests/config.spec.ts +111 -0
- package/vendor/dsh-llm-pi-ai/tests/context.spec.ts +474 -0
- package/vendor/dsh-llm-pi-ai/tests/convert.spec.ts +922 -0
- package/vendor/dsh-llm-pi-ai/tests/discovery.spec.ts +374 -0
- package/vendor/dsh-llm-pi-ai/tests/dynamic-config.spec.ts +241 -0
- package/vendor/dsh-llm-pi-ai/tests/fixtures/qr-code.png +0 -0
- package/vendor/dsh-llm-pi-ai/tests/loader-composition.spec.ts +244 -0
- package/vendor/dsh-llm-pi-ai/tests/login.spec.ts +198 -0
- package/vendor/dsh-llm-pi-ai/tests/mock-server.ts +82 -0
- package/vendor/dsh-llm-pi-ai/tests/provider-apis.e2e.ts +266 -0
- package/vendor/dsh-llm-pi-ai/tests/sdk-options.spec.ts +106 -0
- package/vendor/dsh-llm-pi-ai/tsconfig.json +4 -0
- package/vendor/dsh-llm-pi-ai/tsconfig.upstream.json +51 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
3
|
+
import { Context } from '@deepseek-ai/cordis'
|
|
4
|
+
import { AttachmentId, AttachmentStore, ImageVariantId } from '@deepseek-ai/dsh-attachment'
|
|
5
|
+
import type {
|
|
6
|
+
ImageAttachmentLimits,
|
|
7
|
+
ImageAttachmentRef,
|
|
8
|
+
ImageRequestTarget,
|
|
9
|
+
RequestImageAttachment,
|
|
10
|
+
SaveImageAttachment,
|
|
11
|
+
StoredImageAttachment,
|
|
12
|
+
} from '@deepseek-ai/dsh-attachment'
|
|
13
|
+
import LlmRuntime, { createUserMessage, ToolCallId } from '@deepseek-ai/dsh-llm'
|
|
14
|
+
import type { Message, ToolSchema } from '@deepseek-ai/dsh-llm'
|
|
15
|
+
import * as LlmPiAi from '@deepseek-ai/dsh-llm-pi-ai'
|
|
16
|
+
import type { PiAiReplayResponse } from '../src/replay.ts'
|
|
17
|
+
import { assemble, type AssembledResult } from './assemble.ts'
|
|
18
|
+
|
|
19
|
+
interface ProviderCase {
|
|
20
|
+
provider: 'openai' | 'anthropic'
|
|
21
|
+
api: 'openai-responses' | 'anthropic-messages'
|
|
22
|
+
model: string
|
|
23
|
+
apiKey?: string
|
|
24
|
+
baseURL?: string
|
|
25
|
+
headers?: Record<string, string>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const openAIBaseURL = process.env.DSH_PI_AI_OPENAI_BASE_URL
|
|
29
|
+
const azureOpenAIKey = process.env.AZURE_OPENAI_API_KEY
|
|
30
|
+
// Strictly ANTHROPIC_*: the DeepSeek endpoint does not serve the anthropic-messages
|
|
31
|
+
// protocol, so falling back to DEEPSEEK_API_KEY turns the keyless skip into a 404.
|
|
32
|
+
const anthropicApiKey = process.env.ANTHROPIC_API_KEY
|
|
33
|
+
const anthropicBaseURL = process.env.DSH_PI_AI_ANTHROPIC_BASE_URL
|
|
34
|
+
|
|
35
|
+
const providerCases: ProviderCase[] = [
|
|
36
|
+
{
|
|
37
|
+
provider: 'openai',
|
|
38
|
+
api: 'openai-responses',
|
|
39
|
+
model: process.env.DSH_PI_AI_OPENAI_MODEL ?? 'gpt-5.5',
|
|
40
|
+
...azureOpenAIKey
|
|
41
|
+
? { apiKey: azureOpenAIKey, headers: { 'api-key': azureOpenAIKey, Authorization: '' } }
|
|
42
|
+
: {},
|
|
43
|
+
...openAIBaseURL ? { baseURL: openAIBaseURL } : {},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
provider: 'anthropic',
|
|
47
|
+
api: 'anthropic-messages',
|
|
48
|
+
model: process.env.DSH_PI_AI_ANTHROPIC_MODEL ?? 'claude-opus-4-8',
|
|
49
|
+
...anthropicApiKey === undefined ? {} : { apiKey: anthropicApiKey },
|
|
50
|
+
...anthropicBaseURL === undefined ? {} : { baseURL: anthropicBaseURL },
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
const contexts: Context[] = []
|
|
55
|
+
|
|
56
|
+
async function harness(image?: StoredImageAttachment): Promise<Context> {
|
|
57
|
+
const ctx = new Context()
|
|
58
|
+
contexts.push(ctx)
|
|
59
|
+
await ctx.plugin(LlmRuntime)
|
|
60
|
+
await ctx.plugin(LlmPiAi, {
|
|
61
|
+
providers: Object.fromEntries(providerCases.map(profile => [profile.provider, {
|
|
62
|
+
...profile.apiKey === undefined ? {} : { apiKey: profile.apiKey },
|
|
63
|
+
...profile.baseURL === undefined ? {} : { baseURL: profile.baseURL },
|
|
64
|
+
...profile.headers === undefined ? {} : { headers: profile.headers },
|
|
65
|
+
}])),
|
|
66
|
+
})
|
|
67
|
+
if (image !== undefined) {
|
|
68
|
+
const fixture = image
|
|
69
|
+
class E2eAttachmentStore extends AttachmentStore {
|
|
70
|
+
readonly imageLimits: ImageAttachmentLimits = {
|
|
71
|
+
maxImageBytes: fixture.data.byteLength,
|
|
72
|
+
maxImagesPerMessage: 1,
|
|
73
|
+
maxMessageImageBytes: fixture.data.byteLength,
|
|
74
|
+
maxImagePixels: fixture.ref.width * fixture.ref.height,
|
|
75
|
+
maxImageDimension: Math.max(fixture.ref.width, fixture.ref.height),
|
|
76
|
+
mediaTypes: [fixture.ref.mediaType],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
validateImage(_input: SaveImageAttachment): Promise<void> {
|
|
80
|
+
return Promise.reject(new Error('e2e attachment fixture is read-only'))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
saveImage(_input: SaveImageAttachment): Promise<ImageAttachmentRef> {
|
|
84
|
+
return Promise.reject(new Error('e2e attachment fixture is read-only'))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
readImage(ref: ImageAttachmentRef): Promise<StoredImageAttachment> {
|
|
88
|
+
if (ref.attachmentId !== fixture.ref.attachmentId) {
|
|
89
|
+
return Promise.reject(new Error('unknown e2e attachment fixture'))
|
|
90
|
+
}
|
|
91
|
+
return Promise.resolve(fixture)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
override readImageRequest(ref: ImageAttachmentRef, _policy: ImageRequestTarget): Promise<RequestImageAttachment> {
|
|
95
|
+
if (ref.attachmentId !== fixture.ref.attachmentId) {
|
|
96
|
+
return Promise.reject(new Error('unknown e2e attachment fixture'))
|
|
97
|
+
}
|
|
98
|
+
return Promise.resolve({
|
|
99
|
+
variantId: ImageVariantId(`sha256:${'f'.repeat(64)}`),
|
|
100
|
+
attachment: fixture.ref,
|
|
101
|
+
data: fixture.data,
|
|
102
|
+
mediaType: fixture.ref.mediaType,
|
|
103
|
+
bytes: fixture.data.byteLength,
|
|
104
|
+
width: fixture.ref.width,
|
|
105
|
+
height: fixture.ref.height,
|
|
106
|
+
depth: 'uchar',
|
|
107
|
+
space: 'srgb',
|
|
108
|
+
hasAlpha: fixture.ref.mediaType === 'image/png',
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
await ctx.plugin(E2eAttachmentStore)
|
|
113
|
+
}
|
|
114
|
+
return ctx
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
afterEach(async () => {
|
|
118
|
+
await Promise.all(contexts.splice(0).map(ctx => ctx.fiber.dispose()))
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
function ask(text: string): Message[] {
|
|
122
|
+
return [createUserMessage({
|
|
123
|
+
content: [{ type: 'text', text }],
|
|
124
|
+
source: { kind: 'plugin', plugin: 'test' },
|
|
125
|
+
})]
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function textOf(result: AssembledResult): string {
|
|
129
|
+
return result.message.content
|
|
130
|
+
.filter(block => block.type === 'text')
|
|
131
|
+
.map(block => block.text)
|
|
132
|
+
.join('')
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function expectFinish(result: AssembledResult, expected: 'stop' | 'tool-calls'): void {
|
|
136
|
+
if (result.finish.kind === 'error') {
|
|
137
|
+
throw new Error(`provider request failed (${result.finish.failure.code}): ${result.finish.failure.message}`)
|
|
138
|
+
}
|
|
139
|
+
expect(result.finish.kind).toBe(expected)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function expectNativeReplay(result: AssembledResult, profile: ProviderCase): PiAiReplayResponse {
|
|
143
|
+
const replayState = result.message.source.kind === 'model'
|
|
144
|
+
? result.message.source.replayState
|
|
145
|
+
: undefined
|
|
146
|
+
expect(replayState).toMatchObject({
|
|
147
|
+
response: {
|
|
148
|
+
kind: 'pi-ai',
|
|
149
|
+
version: 2,
|
|
150
|
+
api: profile.api,
|
|
151
|
+
provider: profile.provider,
|
|
152
|
+
model: profile.model,
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
return (replayState as { response: PiAiReplayResponse }).response
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const lookupTool: ToolSchema = {
|
|
159
|
+
name: 'lookup_code',
|
|
160
|
+
description: 'Look up the word represented by a short code.',
|
|
161
|
+
parameters: {
|
|
162
|
+
type: 'object',
|
|
163
|
+
properties: { code: { type: 'string', description: 'The code to look up.' } },
|
|
164
|
+
required: ['code'],
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const profile of providerCases) {
|
|
169
|
+
describe.skipIf(profile.apiKey === undefined)(
|
|
170
|
+
`llm-pi-ai ${profile.provider} e2e (${profile.api})`,
|
|
171
|
+
() => {
|
|
172
|
+
it('streams text with usage and native replay metadata', async () => {
|
|
173
|
+
const ctx = await harness()
|
|
174
|
+
const result = await assemble(ctx, {
|
|
175
|
+
provider: profile.provider,
|
|
176
|
+
model: profile.model,
|
|
177
|
+
messages: ask('Reply with exactly the word: pong'),
|
|
178
|
+
maxTokens: 1024,
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
expectFinish(result, 'stop')
|
|
182
|
+
expect(textOf(result).toLowerCase()).toContain('pong')
|
|
183
|
+
expect(result.usage?.inputTokens).toBeGreaterThan(0)
|
|
184
|
+
expect(result.usage?.outputTokens).toBeGreaterThan(0)
|
|
185
|
+
expect(expectNativeReplay(result, profile).stopReason).toBe('stop')
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('round-trips a tool call with provider-native replay metadata', async () => {
|
|
189
|
+
const ctx = await harness()
|
|
190
|
+
const prompt = ask('Use lookup_code with code "blue". Do not answer without calling the tool.')
|
|
191
|
+
const first = await assemble(ctx, {
|
|
192
|
+
provider: profile.provider,
|
|
193
|
+
model: profile.model,
|
|
194
|
+
messages: prompt,
|
|
195
|
+
tools: [lookupTool],
|
|
196
|
+
maxTokens: 2048,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
expectFinish(first, 'tool-calls')
|
|
200
|
+
const call = first.message.content.find(block => block.type === 'tool-call')
|
|
201
|
+
expect(call).toBeDefined()
|
|
202
|
+
expect(call!.name).toBe('lookup_code')
|
|
203
|
+
expect(JSON.parse(call!.arguments)).toMatchObject({ code: 'blue' })
|
|
204
|
+
expect(expectNativeReplay(first, profile).stopReason).toBe('toolUse')
|
|
205
|
+
|
|
206
|
+
const second = await assemble(ctx, {
|
|
207
|
+
provider: profile.provider,
|
|
208
|
+
model: profile.model,
|
|
209
|
+
messages: [
|
|
210
|
+
...prompt,
|
|
211
|
+
first.message,
|
|
212
|
+
createUserMessage({
|
|
213
|
+
content: [{
|
|
214
|
+
type: 'tool-result',
|
|
215
|
+
toolCallId: ToolCallId(call!.id),
|
|
216
|
+
content: [{ type: 'text', text: 'The code blue means ocean.' }],
|
|
217
|
+
}],
|
|
218
|
+
source: { kind: 'plugin', plugin: 'test' },
|
|
219
|
+
}),
|
|
220
|
+
],
|
|
221
|
+
tools: [lookupTool],
|
|
222
|
+
maxTokens: 2048,
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
expectFinish(second, 'stop')
|
|
226
|
+
expect(textOf(second).toLowerCase()).toContain('ocean')
|
|
227
|
+
expect(expectNativeReplay(second, profile).stopReason).toBe('stop')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
if (profile.provider === 'anthropic') {
|
|
231
|
+
it('sends a real image through the authenticated Anthropic visual path', async () => {
|
|
232
|
+
const data = new Uint8Array(await readFile(
|
|
233
|
+
new URL('./fixtures/qr-code.png', import.meta.url),
|
|
234
|
+
))
|
|
235
|
+
const ref: ImageAttachmentRef = {
|
|
236
|
+
attachmentId: AttachmentId(`sha256:${'a'.repeat(64)}`),
|
|
237
|
+
mediaType: 'image/png',
|
|
238
|
+
bytes: data.byteLength,
|
|
239
|
+
width: 256,
|
|
240
|
+
height: 256,
|
|
241
|
+
name: 'qr-code.png',
|
|
242
|
+
}
|
|
243
|
+
const ctx = await harness({ ref, data })
|
|
244
|
+
const result = await assemble(ctx, {
|
|
245
|
+
provider: profile.provider,
|
|
246
|
+
model: profile.model,
|
|
247
|
+
messages: [createUserMessage({
|
|
248
|
+
content: [
|
|
249
|
+
{
|
|
250
|
+
type: 'text',
|
|
251
|
+
text: 'What type of machine-readable symbol is shown in the attached image? Reply with exactly: QR code',
|
|
252
|
+
},
|
|
253
|
+
{ type: 'image', attachment: ref },
|
|
254
|
+
],
|
|
255
|
+
source: { kind: 'plugin', plugin: 'test' },
|
|
256
|
+
})],
|
|
257
|
+
maxTokens: 256,
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
expectFinish(result, 'stop')
|
|
261
|
+
expect(textOf(result).toLowerCase()).toContain('qr code')
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
)
|
|
266
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { StreamChunk } from '@deepseek-ai/dsh-llm'
|
|
3
|
+
import { attributionHeaders } from '@deepseek-ai/dsh-llm'
|
|
4
|
+
|
|
5
|
+
const streamSimple = vi.hoisted(() => vi.fn())
|
|
6
|
+
|
|
7
|
+
// A hand-declared route is built by `createProvider` over the protocol table in
|
|
8
|
+
// `src/provider.ts`, so the table's lazy api module is the SDK boundary this
|
|
9
|
+
// test can observe. A catalog route dispatches through pi-ai's own provider and
|
|
10
|
+
// would not see this mock.
|
|
11
|
+
vi.mock('@earendil-works/pi-ai/api/openai-completions.lazy', () => ({
|
|
12
|
+
openAICompletionsApi: () => ({ stream: streamSimple, streamSimple }),
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
import { PiAiAdapter } from '../src/adapter.ts'
|
|
16
|
+
import { resolveProfiles } from '../src/config.ts'
|
|
17
|
+
import { memoryAuth } from './auth-double.ts'
|
|
18
|
+
|
|
19
|
+
afterEach(() => { streamSimple.mockReset() })
|
|
20
|
+
|
|
21
|
+
/** A hand-declared OpenAI-compatible route with one fully described model. */
|
|
22
|
+
function gatewayAdapter(reasoningSplit?: boolean): PiAiAdapter {
|
|
23
|
+
return new PiAiAdapter({
|
|
24
|
+
profiles: () => resolveProfiles({
|
|
25
|
+
'local-gateway': {
|
|
26
|
+
api: 'openai-completions',
|
|
27
|
+
baseURL: 'http://127.0.0.1:9/v1',
|
|
28
|
+
models: [{ id: 'local-model', contextWindow: 8192, maxTokens: 1024 }],
|
|
29
|
+
...reasoningSplit === undefined ? {} : { reasoningSplit },
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
32
|
+
resolveAuth: () => Promise.resolve({ apiKey: 'test-key' }),
|
|
33
|
+
auth: memoryAuth(),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function drain(adapter: PiAiAdapter): Promise<StreamChunk[]> {
|
|
38
|
+
const chunks: StreamChunk[] = []
|
|
39
|
+
for await (const chunk of adapter.stream({
|
|
40
|
+
provider: 'local-gateway',
|
|
41
|
+
model: 'local-model',
|
|
42
|
+
messages: [],
|
|
43
|
+
})) chunks.push(chunk)
|
|
44
|
+
return chunks
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
describe('pi-ai SDK retry boundary', () => {
|
|
48
|
+
it.each([true, false])('passes an explicit reasoningSplit=%s without changing reasoning effort', async (reasoningSplit) => {
|
|
49
|
+
streamSimple.mockImplementation(() => { throw new Error('mock SDK boundary') })
|
|
50
|
+
await drain(gatewayAdapter(reasoningSplit))
|
|
51
|
+
expect(streamSimple.mock.calls[0]?.[2]).toMatchObject({ samplingParams: { reasoning_split: reasoningSplit } })
|
|
52
|
+
expect(streamSimple.mock.calls[0]?.[2]).not.toHaveProperty('reasoning')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('uses private auth headers without allowing profile or auth headers to replace attribution', async () => {
|
|
56
|
+
streamSimple.mockImplementation(() => { throw new Error('mock SDK boundary') })
|
|
57
|
+
const reserved = Object.keys(attributionHeaders())[0]!
|
|
58
|
+
const adapter = new PiAiAdapter({
|
|
59
|
+
profiles: () => resolveProfiles({
|
|
60
|
+
'local-gateway': {
|
|
61
|
+
api: 'openai-completions',
|
|
62
|
+
baseURL: 'http://127.0.0.1:9/v1',
|
|
63
|
+
headers: { authorization: 'profile', 'X-Profile': 'kept', [reserved.toUpperCase()]: 'profile-spoof' },
|
|
64
|
+
models: [{ id: 'local-model', contextWindow: 8192, maxTokens: 1024 }],
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
resolveAuth: () => Promise.resolve({ headers: { Authorization: 'Bearer private', 'X-Private': 'auth', [reserved.toUpperCase()]: 'auth-spoof' } }),
|
|
68
|
+
auth: memoryAuth(),
|
|
69
|
+
})
|
|
70
|
+
await drain(adapter)
|
|
71
|
+
expect(streamSimple.mock.calls[0]?.[2]).toMatchObject({ headers: {
|
|
72
|
+
'X-Profile': 'kept', Authorization: 'Bearer private', 'X-Private': 'auth', ...attributionHeaders(),
|
|
73
|
+
} })
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('pins one SDK attempt even when the installed provider currently defaults to zero retries', async () => {
|
|
77
|
+
streamSimple.mockImplementation(() => { throw new Error('mock SDK boundary') })
|
|
78
|
+
|
|
79
|
+
const chunks = await drain(gatewayAdapter())
|
|
80
|
+
|
|
81
|
+
expect(streamSimple).toHaveBeenCalledOnce()
|
|
82
|
+
expect(streamSimple.mock.calls[0]?.[2]).toMatchObject({ maxRetries: 0, apiKey: 'test-key' })
|
|
83
|
+
expect(streamSimple.mock.calls[0]?.[2]).not.toHaveProperty('samplingParams')
|
|
84
|
+
// pi-ai reports a setup failure as a terminal in-stream error rather than
|
|
85
|
+
// throwing, which the converter turns into the harness error finish.
|
|
86
|
+
expect(chunks.at(-1)).toMatchObject({
|
|
87
|
+
type: 'finish',
|
|
88
|
+
reason: { kind: 'error', failure: { message: 'mock SDK boundary' } },
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('dispatches a hand-declared route to the endpoint and model its configuration describes', async () => {
|
|
93
|
+
streamSimple.mockImplementation(() => { throw new Error('mock SDK boundary') })
|
|
94
|
+
|
|
95
|
+
await drain(gatewayAdapter())
|
|
96
|
+
|
|
97
|
+
expect(streamSimple.mock.calls[0]?.[0]).toMatchObject({
|
|
98
|
+
id: 'local-model',
|
|
99
|
+
provider: 'local-gateway',
|
|
100
|
+
api: 'openai-completions',
|
|
101
|
+
baseUrl: 'http://127.0.0.1:9/v1',
|
|
102
|
+
contextWindow: 8192,
|
|
103
|
+
maxTokens: 1024,
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "lib/types"
|
|
6
|
+
},
|
|
7
|
+
"include": [
|
|
8
|
+
"src"
|
|
9
|
+
],
|
|
10
|
+
"references": [
|
|
11
|
+
{
|
|
12
|
+
"path": "../../util/launch-environment"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"path": "../../util/brand"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"path": "../../../vendor/cosmokit"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"path": "../../../vendor/cordis"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"path": "../../../vendor/schemastery"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"path": "../../llm/llm"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"path": "../../attachment/attachment"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"path": "../../fs/fs"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"path": "../../credentials/credentials"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"path": "../../credentials/authorization"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"path": "../../settings/settings"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"path": "../../runtime-diagnostics/invariants"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"path": "../../util/timeout"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|