@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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +124 -0
  3. package/README.zh.md +133 -0
  4. package/cordis.patch.yml +4 -0
  5. package/cordis.web.patch.yml +12 -0
  6. package/docs/chatcode-login.md +88 -0
  7. package/docs/chatcode-login.zh.md +179 -0
  8. package/docs/chatcode-models.md +29 -0
  9. package/docs/chatcode-models.zh.md +29 -0
  10. package/docs/chatcode-reporting.md +96 -0
  11. package/docs/chatcode-reporting.zh.md +96 -0
  12. package/docs/decisions/2026-08-31-chatcode-model-source.md +39 -0
  13. package/docs/decisions/2026-08-31-chatcode-model-source.zh.md +39 -0
  14. package/docs/decisions/2026-09-16-actual-model-adapter-routing.md +31 -0
  15. package/docs/decisions/2026-09-16-actual-model-adapter-routing.zh.md +31 -0
  16. package/lib/client.js +469 -0
  17. package/lib/index.d.ts +263 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +4873 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/startup-gate-BaCbWaKH.js +164 -0
  22. package/lib/startup-gate-BaCbWaKH.js.map +1 -0
  23. package/lib/web-startup.d.ts +9 -0
  24. package/lib/web-startup.d.ts.map +1 -0
  25. package/lib/web-startup.js +20 -0
  26. package/lib/web-startup.js.map +1 -0
  27. package/package.json +121 -0
  28. package/vendor/README.md +7 -0
  29. package/vendor/dsh-llm-pi-ai/LICENSE +21 -0
  30. package/vendor/dsh-llm-pi-ai/README.i18n.yaml +6 -0
  31. package/vendor/dsh-llm-pi-ai/README.md +238 -0
  32. package/vendor/dsh-llm-pi-ai/README.zh.md +238 -0
  33. package/vendor/dsh-llm-pi-ai/package.json +65 -0
  34. package/vendor/dsh-llm-pi-ai/src/adapter.ts +434 -0
  35. package/vendor/dsh-llm-pi-ai/src/auth.ts +241 -0
  36. package/vendor/dsh-llm-pi-ai/src/catalog.ts +908 -0
  37. package/vendor/dsh-llm-pi-ai/src/config.ts +478 -0
  38. package/vendor/dsh-llm-pi-ai/src/context.ts +349 -0
  39. package/vendor/dsh-llm-pi-ai/src/discovery.ts +284 -0
  40. package/vendor/dsh-llm-pi-ai/src/index.ts +336 -0
  41. package/vendor/dsh-llm-pi-ai/src/invariant.ts +30 -0
  42. package/vendor/dsh-llm-pi-ai/src/login.ts +161 -0
  43. package/vendor/dsh-llm-pi-ai/src/provider.ts +192 -0
  44. package/vendor/dsh-llm-pi-ai/src/replay.ts +249 -0
  45. package/vendor/dsh-llm-pi-ai/src/stream.ts +232 -0
  46. package/vendor/dsh-llm-pi-ai/tests/adapter.e2e.ts +168 -0
  47. package/vendor/dsh-llm-pi-ai/tests/adapter.spec.ts +1034 -0
  48. package/vendor/dsh-llm-pi-ai/tests/assemble.ts +32 -0
  49. package/vendor/dsh-llm-pi-ai/tests/auth-double.ts +39 -0
  50. package/vendor/dsh-llm-pi-ai/tests/auth.spec.ts +221 -0
  51. package/vendor/dsh-llm-pi-ai/tests/catalog.spec.ts +1220 -0
  52. package/vendor/dsh-llm-pi-ai/tests/config.spec.ts +111 -0
  53. package/vendor/dsh-llm-pi-ai/tests/context.spec.ts +474 -0
  54. package/vendor/dsh-llm-pi-ai/tests/convert.spec.ts +922 -0
  55. package/vendor/dsh-llm-pi-ai/tests/discovery.spec.ts +374 -0
  56. package/vendor/dsh-llm-pi-ai/tests/dynamic-config.spec.ts +241 -0
  57. package/vendor/dsh-llm-pi-ai/tests/fixtures/qr-code.png +0 -0
  58. package/vendor/dsh-llm-pi-ai/tests/loader-composition.spec.ts +244 -0
  59. package/vendor/dsh-llm-pi-ai/tests/login.spec.ts +198 -0
  60. package/vendor/dsh-llm-pi-ai/tests/mock-server.ts +82 -0
  61. package/vendor/dsh-llm-pi-ai/tests/provider-apis.e2e.ts +266 -0
  62. package/vendor/dsh-llm-pi-ai/tests/sdk-options.spec.ts +106 -0
  63. package/vendor/dsh-llm-pi-ai/tsconfig.json +4 -0
  64. 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,4 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "include": ["src/**/*.ts", "tests/**/*.ts"]
4
+ }
@@ -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
+ }