@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,32 @@
1
+ /**
2
+ * Test helper: drive `ctx.llm.stream()` through a `BlockAssembler` and return
3
+ * the assembled message + usage + finish reason. This exercises the same
4
+ * streaming path production uses (the loop), rather than a service-level
5
+ * one-shot convenience method.
6
+ */
7
+
8
+ import { BlockAssembler } from '@deepseek-ai/dsh-llm'
9
+ import type { Context } from '@deepseek-ai/cordis'
10
+ import type { FinishReason, GenerateOptions, Message, TokenUsage } from '@deepseek-ai/dsh-llm'
11
+
12
+ export interface AssembledResult {
13
+ message: Message
14
+ usage?: TokenUsage
15
+ finish: FinishReason
16
+ }
17
+
18
+ export async function assemble(ctx: Context, options: Omit<GenerateOptions, 'provider'> & { provider?: string }): Promise<AssembledResult> {
19
+ const assembler = new BlockAssembler()
20
+ const request = { provider: 'deepseek', ...options }
21
+ for await (const chunk of ctx.llm.stream(request)) assembler.push(chunk)
22
+ return {
23
+ message: assembler.message({
24
+ kind: 'model',
25
+ provider: request.provider,
26
+ model: request.model,
27
+ ...assembler.replayState === undefined ? {} : { replayState: assembler.replayState },
28
+ }),
29
+ ...assembler.usage !== undefined ? { usage: assembler.usage } : {},
30
+ finish: assembler.finish,
31
+ }
32
+ }
@@ -0,0 +1,39 @@
1
+ import type { Credential } from '@earendil-works/pi-ai'
2
+ import type { PiAiAuthInjection } from '../src/adapter.ts'
3
+
4
+ /**
5
+ * The auth injectables for tests that exercise streaming rather than
6
+ * authentication: an in-process credential store and an ambient context that
7
+ * finds nothing. A test needing real records builds the store over
8
+ * `ctx.credentials` instead, through `credentialStoreFrom`.
9
+ * @param seed - credentials to start with, by pi-ai provider id.
10
+ * @returns the injection to hand `PiAiAdapter`, with its store readable.
11
+ */
12
+ export function memoryAuth(seed: Record<string, Credential> = {}): PiAiAuthInjection & {
13
+ stored: Map<string, Credential>
14
+ } {
15
+ const stored = new Map(Object.entries(seed))
16
+ return {
17
+ stored,
18
+ credentials: {
19
+ read: id => Promise.resolve(stored.get(id)),
20
+ list: () => Promise.resolve([...stored].map(([providerId, credential]) => ({
21
+ providerId,
22
+ type: credential.type,
23
+ }))),
24
+ async modify(id, mutate) {
25
+ const next = await mutate(stored.get(id))
26
+ if (next !== undefined) stored.set(id, next)
27
+ return stored.get(id)
28
+ },
29
+ delete: (id) => {
30
+ stored.delete(id)
31
+ return Promise.resolve()
32
+ },
33
+ },
34
+ authContext: {
35
+ env: () => Promise.resolve(undefined),
36
+ fileExists: () => Promise.resolve(false),
37
+ },
38
+ }
39
+ }
@@ -0,0 +1,221 @@
1
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { Context } from '@deepseek-ai/cordis'
6
+ import LocalCredentialProvider from '@deepseek-ai/dsh-credentials-local'
7
+ import { credentialKey, credentialRef } from '@deepseek-ai/dsh-credentials'
8
+ import { authContextFrom, credentialStoreFrom, isolatedPiAiAuth, recordKeyFor } from '../src/auth.ts'
9
+
10
+ const CODEX = recordKeyFor('openai-codex')
11
+
12
+ const dirs: string[] = []
13
+
14
+ /** A context whose credential records live in a throwaway `$DSH_HOME`. */
15
+ async function stored(): Promise<Context> {
16
+ const dir = await mkdtemp(join(tmpdir(), 'dsh-pi-auth-'))
17
+ dirs.push(dir)
18
+ const ctx = new Context()
19
+ await ctx.plugin(LocalCredentialProvider, { path: join(dir, '.credentials.yaml'), watch: false })
20
+ return ctx
21
+ }
22
+
23
+ afterEach(async () => {
24
+ vi.unstubAllEnvs()
25
+ await Promise.all(dirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
26
+ })
27
+
28
+ describe('pi-ai credential store over harness records', () => {
29
+ it('keeps standalone adapter credentials private to each instance', async () => {
30
+ const first = isolatedPiAiAuth()
31
+ const second = isolatedPiAiAuth()
32
+ await first.credentials.modify('gateway', () => Promise.resolve({ type: 'api_key', key: 'private' }))
33
+ await expect(first.credentials.read('gateway')).resolves.toEqual({ type: 'api_key', key: 'private' })
34
+ await expect(second.credentials.read('gateway')).resolves.toBeUndefined()
35
+ })
36
+
37
+ it('reads nothing for a provider with no record', async () => {
38
+ const store = credentialStoreFrom(await stored())
39
+
40
+ await expect(store.read('openai-codex')).resolves.toBeUndefined()
41
+ })
42
+
43
+ it('round-trips an api-key credential field by field', async () => {
44
+ const ctx = await stored()
45
+ const store = credentialStoreFrom(ctx)
46
+
47
+ await store.modify('cloudflare', () =>
48
+ Promise.resolve({ type: 'api_key', key: 'sk-live', env: { ACCOUNT_ID: 'acct-1' } }))
49
+
50
+ await expect(store.read('cloudflare'))
51
+ .resolves.toEqual({ type: 'api_key', key: 'sk-live', env: { ACCOUNT_ID: 'acct-1' } })
52
+ await expect(ctx.credentials.readRecord(recordKeyFor('cloudflare')))
53
+ .resolves.toEqual({ kind: 'api-key', key: 'sk-live', env: { ACCOUNT_ID: 'acct-1' } })
54
+ })
55
+
56
+ it('stores an api-key credential carrying neither a key nor env', async () => {
57
+ const store = credentialStoreFrom(await stored())
58
+
59
+ await store.modify('bedrock', () => Promise.resolve({ type: 'api_key' }))
60
+
61
+ await expect(store.read('bedrock')).resolves.toEqual({ type: 'api_key' })
62
+ })
63
+
64
+ it('keeps an OAuth credential verbatim, refresh fields and all', async () => {
65
+ const ctx = await stored()
66
+ const store = credentialStoreFrom(ctx)
67
+ const granted = { type: 'oauth' as const, access: 'at', refresh: 'rt', expires: 42, accountId: 'acc' }
68
+
69
+ await store.modify('openai-codex', () => Promise.resolve(granted))
70
+
71
+ await expect(store.read('openai-codex')).resolves.toEqual(granted)
72
+ await expect(ctx.credentials.readRecord(CODEX)).resolves.toEqual({ kind: 'grant', payload: granted })
73
+ })
74
+
75
+ it('stores the JSON image of a grant, dropping explicitly-undefined members', async () => {
76
+ const ctx = await stored()
77
+ const store = credentialStoreFrom(ctx)
78
+ // The github.com Copilot shape: pi-ai sets optional members to explicit
79
+ // undefined, which the strict record validator refuses verbatim.
80
+ const granted = {
81
+ type: 'oauth' as const,
82
+ access: 'at',
83
+ refresh: 'rt',
84
+ expires: 42,
85
+ enterpriseUrl: undefined,
86
+ nested: { keep: 'x', drop: undefined },
87
+ list: ['a', undefined, 'b'],
88
+ }
89
+
90
+ await store.modify('github-copilot', () => Promise.resolve(granted))
91
+
92
+ await expect(ctx.credentials.readRecord(recordKeyFor('github-copilot'))).resolves.toEqual({
93
+ kind: 'grant',
94
+ payload: {
95
+ type: 'oauth',
96
+ access: 'at',
97
+ refresh: 'rt',
98
+ expires: 42,
99
+ nested: { keep: 'x' },
100
+ list: ['a', null, 'b'],
101
+ },
102
+ })
103
+ })
104
+
105
+ it('passes a genuinely unstorable grant value through to the store\'s loud refusal', async () => {
106
+ const ctx = await stored()
107
+ const store = credentialStoreFrom(ctx)
108
+ // A foreign-prototype member is not the undefined idiom: the image leaves
109
+ // it untouched and the record validator still refuses the write.
110
+ const granted = { type: 'oauth' as const, access: 'at', refresh: 'rt', expires: 42, issued: new Date(0) }
111
+
112
+ await expect(store.modify('github-copilot', () => Promise.resolve(granted)))
113
+ .rejects.toThrow(/JSON cannot represent/)
114
+ })
115
+
116
+ it('shows the mutation the current credential and leaves it alone when declined', async () => {
117
+ const store = credentialStoreFrom(await stored())
118
+ await store.modify('openai-codex', () =>
119
+ Promise.resolve({ type: 'oauth', access: 'first', refresh: 'r', expires: 1 }))
120
+ const seen: unknown[] = []
121
+
122
+ const unchanged = await store.modify('openai-codex', (current) => {
123
+ seen.push(current)
124
+ return Promise.resolve(undefined)
125
+ })
126
+
127
+ expect(seen).toEqual([{ type: 'oauth', access: 'first', refresh: 'r', expires: 1 }])
128
+ expect(unchanged).toEqual({ type: 'oauth', access: 'first', refresh: 'r', expires: 1 })
129
+ })
130
+
131
+ it('lists only the records this adapter family owns', async () => {
132
+ const ctx = await stored()
133
+ const store = credentialStoreFrom(ctx)
134
+ await store.modify('openai-codex', () =>
135
+ Promise.resolve({ type: 'oauth', access: 'at', refresh: 'rt', expires: 1 }))
136
+ await store.modify('cloudflare', () => Promise.resolve({ type: 'api_key', key: 'k' }))
137
+ // Another plugin's record for a provider name this one also serves: its
138
+ // payload is written in a format pi-ai never agreed to.
139
+ await ctx.credentials.modifyRecord(credentialKey('llm-kimi', 'openai-codex'), () =>
140
+ Promise.resolve({ kind: 'grant', payload: { theirs: true } }))
141
+
142
+ await expect(store.list()).resolves.toEqual([
143
+ { providerId: 'openai-codex', type: 'oauth' },
144
+ { providerId: 'cloudflare', type: 'api_key' },
145
+ ])
146
+ })
147
+
148
+ it('forgets a credential on delete, and stays quiet when there was none', async () => {
149
+ const store = credentialStoreFrom(await stored())
150
+ await store.modify('openai-codex', () =>
151
+ Promise.resolve({ type: 'oauth', access: 'at', refresh: 'rt', expires: 1 }))
152
+
153
+ await store.delete('openai-codex')
154
+ await store.delete('openai-codex')
155
+
156
+ await expect(store.read('openai-codex')).resolves.toBeUndefined()
157
+ })
158
+
159
+ it('reads empty but refuses to write without a credentials service', async () => {
160
+ const store = credentialStoreFrom(new Context())
161
+
162
+ await expect(store.read('openai-codex')).resolves.toBeUndefined()
163
+ await expect(store.list()).resolves.toEqual([])
164
+ await expect(store.modify('openai-codex', () => Promise.resolve({ type: 'api_key', key: 'k' })))
165
+ .rejects.toThrow(/mounts no credentials service/)
166
+ await expect(store.delete('openai-codex')).rejects.toThrow(/mounts no credentials service/)
167
+ })
168
+
169
+ it('treats a provider id outside the record grammar as holding nothing', async () => {
170
+ const store = credentialStoreFrom(await stored())
171
+
172
+ // A hand-declared route key is an arbitrary settings dict key, and pi-ai
173
+ // reads it during auth resolution: the answer is "not signed in", never a
174
+ // thrown address error…
175
+ await expect(store.read('My_Proxy')).resolves.toBeUndefined()
176
+ // …nothing can ever be stored under it, so a logout has nothing to remove…
177
+ await expect(store.delete('My_Proxy')).resolves.toBeUndefined()
178
+ // …while a write that cannot land must refuse rather than report success.
179
+ await expect(store.modify('My_Proxy', () => Promise.resolve({ type: 'api_key', key: 'k' })))
180
+ .rejects.toThrow(/cannot address a stored credential record/)
181
+ })
182
+ })
183
+
184
+ describe('pi-ai ambient auth context', () => {
185
+ beforeEach(() => {
186
+ vi.stubEnv('PI_AUTH_AMBIENT', 'from-environment')
187
+ })
188
+
189
+ it('answers an environment name from the credential seam first', async () => {
190
+ const ctx = await stored()
191
+ await ctx.credentials.set(credentialRef('PI_AUTH_SEAM'), 'from-seam')
192
+
193
+ await expect(authContextFrom(ctx).env('PI_AUTH_SEAM')).resolves.toBe('from-seam')
194
+ })
195
+
196
+ it('falls back to the launch environment when nothing is stored', async () => {
197
+ await expect(authContextFrom(await stored()).env('PI_AUTH_AMBIENT')).resolves.toBe('from-environment')
198
+ })
199
+
200
+ it('answers "not set" for a name no reference could ever address', async () => {
201
+ // pi-ai asks about provider-declared names; one outside the reference
202
+ // grammar has no reference to miss, and must not throw.
203
+ await expect(authContextFrom(await stored()).env('not a var')).resolves.toBeUndefined()
204
+ })
205
+
206
+ it('answers about the host filesystem, expanding a leading ~', async () => {
207
+ const dir = await mkdtemp(join(tmpdir(), 'dsh-pi-home-'))
208
+ dirs.push(dir)
209
+ await writeFile(join(dir, 'creds'), 'x')
210
+ // Both spellings of "home": os.homedir() reads HOME on POSIX and
211
+ // USERPROFILE on Windows, and the expansion under test goes through it.
212
+ vi.stubEnv('HOME', dir)
213
+ vi.stubEnv('USERPROFILE', dir)
214
+ const context = authContextFrom(await stored())
215
+
216
+ await expect(context.fileExists('~/creds')).resolves.toBe(true)
217
+ await expect(context.fileExists('~/missing')).resolves.toBe(false)
218
+ await expect(context.fileExists(join(dir, 'creds'))).resolves.toBe(true)
219
+ await expect(context.fileExists('~')).resolves.toBe(true)
220
+ })
221
+ })