@jasonews/agent-squad-gateway 1.0.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 (149) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +200 -0
  3. package/SECURITY.md +44 -0
  4. package/dist/bin/agent-squad-gateway.js +55 -0
  5. package/dist/bin/agent-squad-gateway.js.map +1 -0
  6. package/dist/config/config.js +124 -0
  7. package/dist/config/config.js.map +1 -0
  8. package/dist/config/paths.js +16 -0
  9. package/dist/config/paths.js.map +1 -0
  10. package/dist/control-plane/clients.js +77 -0
  11. package/dist/control-plane/clients.js.map +1 -0
  12. package/dist/control-plane/core-connection.js +54 -0
  13. package/dist/control-plane/core-connection.js.map +1 -0
  14. package/dist/control-plane/credentials.js +134 -0
  15. package/dist/control-plane/credentials.js.map +1 -0
  16. package/dist/control-plane/db.js +33 -0
  17. package/dist/control-plane/db.js.map +1 -0
  18. package/dist/control-plane/extensions.js +59 -0
  19. package/dist/control-plane/extensions.js.map +1 -0
  20. package/dist/control-plane/grants.js +107 -0
  21. package/dist/control-plane/grants.js.map +1 -0
  22. package/dist/control-plane/idempotency.js +132 -0
  23. package/dist/control-plane/idempotency.js.map +1 -0
  24. package/dist/control-plane/migrations.js +133 -0
  25. package/dist/control-plane/migrations.js.map +1 -0
  26. package/dist/control-plane/response-sessions.js +359 -0
  27. package/dist/control-plane/response-sessions.js.map +1 -0
  28. package/dist/control-plane/retention.js +138 -0
  29. package/dist/control-plane/retention.js.map +1 -0
  30. package/dist/control-plane/runs.js +156 -0
  31. package/dist/control-plane/runs.js.map +1 -0
  32. package/dist/control-plane/targets.js +449 -0
  33. package/dist/control-plane/targets.js.map +1 -0
  34. package/dist/control-plane/types.js +2 -0
  35. package/dist/control-plane/types.js.map +1 -0
  36. package/dist/core-client/client.js +275 -0
  37. package/dist/core-client/client.js.map +1 -0
  38. package/dist/core-client/event-proxy.js +105 -0
  39. package/dist/core-client/event-proxy.js.map +1 -0
  40. package/dist/core-client/types.js +2 -0
  41. package/dist/core-client/types.js.map +1 -0
  42. package/dist/extensions/contract.js +2 -0
  43. package/dist/extensions/contract.js.map +1 -0
  44. package/dist/extensions/openai/auth.js +16 -0
  45. package/dist/extensions/openai/auth.js.map +1 -0
  46. package/dist/extensions/openai/chat.js +403 -0
  47. package/dist/extensions/openai/chat.js.map +1 -0
  48. package/dist/extensions/openai/errors.js +79 -0
  49. package/dist/extensions/openai/errors.js.map +1 -0
  50. package/dist/extensions/openai/image-input.js +79 -0
  51. package/dist/extensions/openai/image-input.js.map +1 -0
  52. package/dist/extensions/openai/models.js +40 -0
  53. package/dist/extensions/openai/models.js.map +1 -0
  54. package/dist/extensions/openai/responses.js +731 -0
  55. package/dist/extensions/openai/responses.js.map +1 -0
  56. package/dist/extensions/openai/routes.js +81 -0
  57. package/dist/extensions/openai/routes.js.map +1 -0
  58. package/dist/extensions/openai/run-attempt.js +96 -0
  59. package/dist/extensions/openai/run-attempt.js.map +1 -0
  60. package/dist/extensions/openai/schemas.js +176 -0
  61. package/dist/extensions/openai/schemas.js.map +1 -0
  62. package/dist/extensions/openai/sse.js +421 -0
  63. package/dist/extensions/openai/sse.js.map +1 -0
  64. package/dist/extensions/openai/tools.js +727 -0
  65. package/dist/extensions/openai/tools.js.map +1 -0
  66. package/dist/extensions/registry.js +30 -0
  67. package/dist/extensions/registry.js.map +1 -0
  68. package/dist/lifecycle/daemon.js +437 -0
  69. package/dist/lifecycle/daemon.js.map +1 -0
  70. package/dist/lifecycle/pidfile.js +34 -0
  71. package/dist/lifecycle/pidfile.js.map +1 -0
  72. package/dist/provider-runtime/antigravity/adapter.js +398 -0
  73. package/dist/provider-runtime/antigravity/adapter.js.map +1 -0
  74. package/dist/provider-runtime/antigravity/conversation.js +31 -0
  75. package/dist/provider-runtime/antigravity/conversation.js.map +1 -0
  76. package/dist/provider-runtime/capability-service.js +191 -0
  77. package/dist/provider-runtime/capability-service.js.map +1 -0
  78. package/dist/provider-runtime/claude/adapter.js +397 -0
  79. package/dist/provider-runtime/claude/adapter.js.map +1 -0
  80. package/dist/provider-runtime/claude/protocol.js +143 -0
  81. package/dist/provider-runtime/claude/protocol.js.map +1 -0
  82. package/dist/provider-runtime/codex/adapter.js +440 -0
  83. package/dist/provider-runtime/codex/adapter.js.map +1 -0
  84. package/dist/provider-runtime/codex/json-rpc-client.js +182 -0
  85. package/dist/provider-runtime/codex/json-rpc-client.js.map +1 -0
  86. package/dist/provider-runtime/codex/protocol.js +128 -0
  87. package/dist/provider-runtime/codex/protocol.js.map +1 -0
  88. package/dist/provider-runtime/command-discovery.js +106 -0
  89. package/dist/provider-runtime/command-discovery.js.map +1 -0
  90. package/dist/provider-runtime/cursor/adapter.js +427 -0
  91. package/dist/provider-runtime/cursor/adapter.js.map +1 -0
  92. package/dist/provider-runtime/cursor/protocol.js +81 -0
  93. package/dist/provider-runtime/cursor/protocol.js.map +1 -0
  94. package/dist/provider-runtime/fake/adapter.js +138 -0
  95. package/dist/provider-runtime/fake/adapter.js.map +1 -0
  96. package/dist/provider-runtime/image-assets.js +409 -0
  97. package/dist/provider-runtime/image-assets.js.map +1 -0
  98. package/dist/provider-runtime/image-support.js +5 -0
  99. package/dist/provider-runtime/image-support.js.map +1 -0
  100. package/dist/provider-runtime/invocation-service.js +503 -0
  101. package/dist/provider-runtime/invocation-service.js.map +1 -0
  102. package/dist/provider-runtime/kimi/adapter.js +657 -0
  103. package/dist/provider-runtime/kimi/adapter.js.map +1 -0
  104. package/dist/provider-runtime/opencode/adapter.js +610 -0
  105. package/dist/provider-runtime/opencode/adapter.js.map +1 -0
  106. package/dist/provider-runtime/opencode/server.js +46 -0
  107. package/dist/provider-runtime/opencode/server.js.map +1 -0
  108. package/dist/provider-runtime/process/line-reader.js +49 -0
  109. package/dist/provider-runtime/process/line-reader.js.map +1 -0
  110. package/dist/provider-runtime/process/managed-process.js +151 -0
  111. package/dist/provider-runtime/process/managed-process.js.map +1 -0
  112. package/dist/provider-runtime/provider-input.js +77 -0
  113. package/dist/provider-runtime/provider-input.js.map +1 -0
  114. package/dist/provider-runtime/register-providers.js +26 -0
  115. package/dist/provider-runtime/register-providers.js.map +1 -0
  116. package/dist/provider-runtime/registry.js +35 -0
  117. package/dist/provider-runtime/registry.js.map +1 -0
  118. package/dist/provider-runtime/replay-buffer.js +168 -0
  119. package/dist/provider-runtime/replay-buffer.js.map +1 -0
  120. package/dist/provider-runtime/scheduler.js +155 -0
  121. package/dist/provider-runtime/scheduler.js.map +1 -0
  122. package/dist/provider-runtime/types.js +2 -0
  123. package/dist/provider-runtime/types.js.map +1 -0
  124. package/dist/provider-runtime/workspaces.js +814 -0
  125. package/dist/provider-runtime/workspaces.js.map +1 -0
  126. package/dist/security/admin-auth.js +109 -0
  127. package/dist/security/admin-auth.js.map +1 -0
  128. package/dist/security/crypto.js +52 -0
  129. package/dist/security/crypto.js.map +1 -0
  130. package/dist/security/secret-files.js +33 -0
  131. package/dist/security/secret-files.js.map +1 -0
  132. package/dist/server/admin-routes.js +610 -0
  133. package/dist/server/admin-routes.js.map +1 -0
  134. package/dist/server/app.js +178 -0
  135. package/dist/server/app.js.map +1 -0
  136. package/dist/server/auth-hooks.js +43 -0
  137. package/dist/server/auth-hooks.js.map +1 -0
  138. package/dist/server/core-routes.js +127 -0
  139. package/dist/server/core-routes.js.map +1 -0
  140. package/dist/server/errors.js +19 -0
  141. package/dist/server/errors.js.map +1 -0
  142. package/dist/server/ui-routes.js +77 -0
  143. package/dist/server/ui-routes.js.map +1 -0
  144. package/dist/web/assets/index-BhqNoPpE.js +206 -0
  145. package/dist/web/assets/index-BhqNoPpE.js.map +1 -0
  146. package/dist/web/assets/index-C5nnaBfZ.css +1 -0
  147. package/dist/web/index.html +14 -0
  148. package/package.json +74 -0
  149. package/translations/README.zh-CN.md +175 -0
@@ -0,0 +1,657 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { Readable, Writable } from 'node:stream';
3
+ import * as acp from '@agentclientprotocol/sdk';
4
+ import { z } from 'zod';
5
+ import { API_PROVIDER_SYSTEM_INSTRUCTION, renderProviderInput, } from '../provider-input.js';
6
+ import { readBoundedLines } from '../process/line-reader.js';
7
+ import { spawnManagedProcess, } from '../process/managed-process.js';
8
+ const COMMAND_OUTPUT_LIMIT_BYTES = 64 * 1024;
9
+ const DEFAULT_EFFORT_OPTIONS = ['off', 'on'];
10
+ const PROCESS_EXIT_CLASSIFICATION_MS = 25;
11
+ const STATIC_PROBE_TIMEOUT_MS = 5_000;
12
+ const CONFORMANCE_SCHEMA = {
13
+ type: 'object',
14
+ required: ['type', 'content'],
15
+ properties: {
16
+ type: { const: 'assistant_text' },
17
+ content: { type: 'string', minLength: 1 },
18
+ },
19
+ additionalProperties: false,
20
+ };
21
+ const conformanceOutputSchema = z.object({
22
+ type: z.literal('assistant_text'),
23
+ content: z.string().min(1),
24
+ }).strict();
25
+ export const KIMI_ISOLATION_WARNING = [
26
+ 'Kimi Code ACP receives no MCP servers or client filesystem/terminal capabilities, rejects all permission requests,',
27
+ 'and fails on tool updates, but Kimi shell execution remains local to the CLI; isolation is best effort.',
28
+ ].join(' ');
29
+ export const KIMI_SESSION_RETENTION_WARNING = [
30
+ 'Kimi Code ACP does not advertise session deletion, so ephemeral Gateway calls can remain',
31
+ 'in local Kimi session history.',
32
+ ].join(' ');
33
+ class KimiAdapterError extends Error {
34
+ code;
35
+ constructor(code) {
36
+ super(code);
37
+ this.code = code;
38
+ this.name = 'KimiAdapterError';
39
+ }
40
+ }
41
+ class AsyncEventQueue {
42
+ values = [];
43
+ waiters = [];
44
+ terminal = null;
45
+ push(value) {
46
+ if (this.terminal !== null)
47
+ return;
48
+ const waiter = this.waiters.shift();
49
+ if (waiter)
50
+ waiter.resolve(value);
51
+ else
52
+ this.values.push(value);
53
+ }
54
+ end() {
55
+ if (this.terminal !== null)
56
+ return;
57
+ this.terminal = { error: null };
58
+ for (const waiter of this.waiters.splice(0))
59
+ waiter.resolve(null);
60
+ }
61
+ fail(error) {
62
+ if (this.terminal !== null)
63
+ return;
64
+ this.terminal = { error };
65
+ for (const waiter of this.waiters.splice(0))
66
+ waiter.reject(error);
67
+ }
68
+ async take() {
69
+ const value = this.values.shift();
70
+ if (value !== undefined)
71
+ return value;
72
+ if (this.terminal !== null) {
73
+ if (this.terminal.error !== null)
74
+ throw this.terminal.error;
75
+ return null;
76
+ }
77
+ return new Promise((resolve, reject) => {
78
+ this.waiters.push({ resolve, reject });
79
+ });
80
+ }
81
+ }
82
+ function capabilities(version, verified, models = defaultModelOptions()) {
83
+ return {
84
+ available: true,
85
+ version,
86
+ verified,
87
+ ...(verified ? { verifiedAt: new Date().toISOString() } : {}),
88
+ modelSelection: true,
89
+ effortSelection: true,
90
+ modelOptions: models,
91
+ isolationLevel: 'best_effort',
92
+ streamingMode: 'native',
93
+ toolBridge: 'structured_output',
94
+ resume: true,
95
+ cancellation: true,
96
+ details: [KIMI_ISOLATION_WARNING, KIMI_SESSION_RETENTION_WARNING],
97
+ };
98
+ }
99
+ function unavailable(error) {
100
+ return {
101
+ available: false,
102
+ verified: false,
103
+ modelSelection: false,
104
+ effortSelection: false,
105
+ isolationLevel: 'best_effort',
106
+ streamingMode: 'none',
107
+ toolBridge: 'none',
108
+ resume: false,
109
+ cancellation: false,
110
+ error,
111
+ };
112
+ }
113
+ function defaultModelOptions() {
114
+ return [{
115
+ id: 'default',
116
+ label: 'Kimi Code default',
117
+ effortOptions: [...DEFAULT_EFFORT_OPTIONS],
118
+ }];
119
+ }
120
+ function safeProbeError(error) {
121
+ if (error instanceof KimiAdapterError)
122
+ return error.code;
123
+ if (typeof error === 'object' && error !== null && 'code' in error
124
+ && (error.code === 'adapter_spawn_failed' || error.code === 'adapter_process_error')) {
125
+ return String(error.code);
126
+ }
127
+ return 'capability_probe_failed';
128
+ }
129
+ function failureEvent(error, nativeStateAdvanced) {
130
+ const code = error instanceof KimiAdapterError
131
+ ? error.code
132
+ : typeof error === 'object' && error !== null && 'code' in error
133
+ && (error.code === 'adapter_spawn_failed' || error.code === 'adapter_process_error')
134
+ ? String(error.code)
135
+ : 'adapter_protocol_error';
136
+ return {
137
+ type: 'failed',
138
+ code,
139
+ message: code === 'adapter_capability_unsupported'
140
+ ? 'Kimi provider capability unsupported'
141
+ : code === 'adapter_protocol_error'
142
+ ? 'Kimi provider protocol error'
143
+ : 'Kimi provider process error',
144
+ nativeStateAdvanced,
145
+ };
146
+ }
147
+ function processError(process, code) {
148
+ const diagnostic = process.stderrDiagnostic().trim();
149
+ return Object.assign(new Error(diagnostic || code), { code });
150
+ }
151
+ async function disposeQuietly(process) {
152
+ try {
153
+ await process.dispose();
154
+ }
155
+ catch {
156
+ // The ACP process may exit while connection cleanup is in progress.
157
+ }
158
+ }
159
+ function canonicalJson(value) {
160
+ if (Array.isArray(value))
161
+ return `[${value.map(canonicalJson).join(',')}]`;
162
+ if (value !== null && typeof value === 'object') {
163
+ return `{${Object.entries(value)
164
+ .sort(([left], [right]) => left.localeCompare(right))
165
+ .map(([key, nested]) => `${JSON.stringify(key)}:${canonicalJson(nested)}`)
166
+ .join(',')}}`;
167
+ }
168
+ const encoded = JSON.stringify(value);
169
+ if (encoded === undefined)
170
+ throw new KimiAdapterError('adapter_protocol_error');
171
+ return encoded;
172
+ }
173
+ function renderPrompt(request) {
174
+ const sections = [
175
+ API_PROVIDER_SYSTEM_INSTRUCTION,
176
+ renderProviderInput(request.input),
177
+ ];
178
+ if (request.outputSchema !== null) {
179
+ sections.push([
180
+ 'Return exactly one raw JSON value that validates against the following JSON Schema.',
181
+ `JSON Schema: ${canonicalJson(request.outputSchema)}`,
182
+ 'Do not include Markdown fences, commentary, reasoning, or any text outside that JSON value.',
183
+ ].join('\n'));
184
+ }
185
+ return sections.join('\n\n');
186
+ }
187
+ function configMatches(option, category, namePattern) {
188
+ return option.type === 'select'
189
+ && (option.category === category || namePattern.test(`${option.id} ${option.name}`));
190
+ }
191
+ function findModelConfig(options) {
192
+ return options.find((option) => configMatches(option, 'model', /\bmodel\b/i)) ?? null;
193
+ }
194
+ function findEffortConfig(options) {
195
+ return options.find((option) => configMatches(option, 'thought_level', /\b(?:thinking|thought|reasoning|effort)\b/i)) ?? null;
196
+ }
197
+ function selectValues(option) {
198
+ if (option?.type !== 'select')
199
+ return [];
200
+ return option.options.flatMap((entry) => 'options' in entry ? entry.options : [entry]);
201
+ }
202
+ function resolveConfigValue(option, requested, kind) {
203
+ const values = selectValues(option);
204
+ const normalized = requested.toLocaleLowerCase();
205
+ const exact = values.find((candidate) => candidate.value === requested)
206
+ ?? values.find((candidate) => candidate.name.toLocaleLowerCase() === normalized);
207
+ if (exact)
208
+ return exact.value;
209
+ if (kind === 'effort') {
210
+ const enabled = values.find((candidate) => candidate.value === 'on');
211
+ const disabled = values.find((candidate) => candidate.value === 'off');
212
+ if (enabled && disabled) {
213
+ return ['off', 'none', 'false', 'disabled'].includes(normalized)
214
+ ? disabled.value
215
+ : enabled.value;
216
+ }
217
+ }
218
+ return requested;
219
+ }
220
+ function observeConfig(options, observation, includeEfforts = true) {
221
+ if (!observation)
222
+ return;
223
+ for (const value of selectValues(findModelConfig(options))) {
224
+ observation.models.set(value.value, value.name);
225
+ }
226
+ if (includeEfforts) {
227
+ for (const value of selectValues(findEffortConfig(options))) {
228
+ observation.efforts.set(value.value, value.name);
229
+ }
230
+ }
231
+ }
232
+ function conformanceModels(observation, verifiedModel) {
233
+ const efforts = [...observation.efforts.keys()];
234
+ const selectedEfforts = efforts.length > 0 ? [...efforts] : null;
235
+ const result = [{
236
+ id: 'default',
237
+ label: 'Kimi Code default',
238
+ effortOptions: verifiedModel === 'default' ? selectedEfforts : null,
239
+ }];
240
+ for (const [id, label] of observation.models) {
241
+ if (id !== 'default') {
242
+ result.push({ id, label, effortOptions: id === verifiedModel ? selectedEfforts : null });
243
+ }
244
+ }
245
+ return result;
246
+ }
247
+ function rejectPermission(request) {
248
+ const rejection = request.options.find((option) => option.kind === 'reject_always')
249
+ ?? request.options.find((option) => option.kind === 'reject_once');
250
+ return rejection
251
+ ? { outcome: { outcome: 'selected', optionId: rejection.optionId } }
252
+ : { outcome: { outcome: 'cancelled' } };
253
+ }
254
+ function initialClientCapabilities() {
255
+ return {
256
+ session: { configOptions: {} },
257
+ };
258
+ }
259
+ export class KimiProviderAdapter {
260
+ spawnProcess;
261
+ command;
262
+ env;
263
+ staticProbeTimeoutMs;
264
+ launchCwd = process.cwd();
265
+ activeRuns = new Map();
266
+ constructor(options = {}) {
267
+ this.spawnProcess = options.spawnProcess ?? spawnManagedProcess;
268
+ this.command = options.command ?? 'kimi';
269
+ this.env = options.env ?? { ...process.env };
270
+ this.staticProbeTimeoutMs = options.staticProbeTimeoutMs ?? STATIC_PROBE_TIMEOUT_MS;
271
+ }
272
+ async probeCapabilities(request = { mode: 'static' }) {
273
+ try {
274
+ const version = await this.staticMetadata();
275
+ if (request.mode === 'static')
276
+ return capabilities(version, false);
277
+ const result = await this.verifyConformance(request);
278
+ return capabilities(version, true, result.models);
279
+ }
280
+ catch (error) {
281
+ return unavailable(safeProbeError(error));
282
+ }
283
+ }
284
+ start(request) {
285
+ return this.stream(request, null);
286
+ }
287
+ resume(request) {
288
+ return this.stream(request, request.nativeSessionId);
289
+ }
290
+ async cancel(runId) {
291
+ const active = this.activeRuns.get(runId);
292
+ if (!active)
293
+ return;
294
+ active.cancellationRequested = true;
295
+ if (!active.cancelSent && active.context && active.sessionId) {
296
+ active.cancelSent = true;
297
+ try {
298
+ await active.context.notify(acp.methods.agent.session.cancel, {
299
+ sessionId: active.sessionId,
300
+ });
301
+ }
302
+ catch {
303
+ // Process interruption below remains the cancellation backstop.
304
+ }
305
+ }
306
+ await active.process.interrupt();
307
+ }
308
+ async *stream(request, requestedSessionId, observation) {
309
+ if (request.signal.aborted) {
310
+ yield { type: 'cancelled' };
311
+ return;
312
+ }
313
+ const queue = new AsyncEventQueue();
314
+ let managed = null;
315
+ let active = null;
316
+ let producer = null;
317
+ let producerSettled = false;
318
+ let nativeStateAdvanced = false;
319
+ let protocolViolation = false;
320
+ let turnTerminal = false;
321
+ const onAbort = () => { void this.cancel(request.runId).catch(() => undefined); };
322
+ request.signal.addEventListener('abort', onAbort, { once: true });
323
+ try {
324
+ managed = this.spawnProcess({
325
+ command: this.command,
326
+ args: ['acp'],
327
+ cwd: request.workspace,
328
+ env: { ...this.env },
329
+ });
330
+ active = {
331
+ process: managed,
332
+ context: null,
333
+ sessionId: null,
334
+ cancellationRequested: false,
335
+ cancelSent: false,
336
+ };
337
+ this.activeRuns.set(request.runId, active);
338
+ const output = Writable.toWeb(managed.stdin);
339
+ const input = Readable.toWeb(managed.stdout);
340
+ const transport = acp.ndJsonStream(output, input);
341
+ const app = acp.client({ name: 'agent-squad-gateway' })
342
+ .onRequest(acp.methods.client.session.requestPermission, (context) => (rejectPermission(context.params)))
343
+ .onNotification(acp.methods.client.session.update, (context) => {
344
+ const update = context.params.update;
345
+ if (context.params.sessionId !== active?.sessionId) {
346
+ protocolViolation = true;
347
+ queue.fail(new KimiAdapterError('adapter_protocol_error'));
348
+ return;
349
+ }
350
+ if (update.sessionUpdate === 'tool_call'
351
+ || update.sessionUpdate === 'tool_call_update') {
352
+ protocolViolation = true;
353
+ queue.fail(new KimiAdapterError('adapter_protocol_error'));
354
+ if (active && !active.cancelSent) {
355
+ active.cancelSent = true;
356
+ void context.agent.notify(acp.methods.agent.session.cancel, {
357
+ sessionId: context.params.sessionId,
358
+ }).catch(() => undefined);
359
+ }
360
+ return;
361
+ }
362
+ if (update.sessionUpdate === 'agent_message_chunk'
363
+ && update.content.type === 'text') {
364
+ observation && (observation.textChunks += 1);
365
+ queue.push(request.outputSchema === null
366
+ ? { type: 'text_delta', delta: update.content.text }
367
+ : { type: 'structured_delta', delta: update.content.text });
368
+ }
369
+ });
370
+ producer = app.connectWith(transport, async (context) => {
371
+ if (!active)
372
+ throw new KimiAdapterError('adapter_protocol_error');
373
+ active.context = context;
374
+ const init = await context.request(acp.methods.agent.initialize, {
375
+ protocolVersion: acp.PROTOCOL_VERSION,
376
+ clientCapabilities: initialClientCapabilities(),
377
+ clientInfo: {
378
+ name: 'agent-squad-gateway',
379
+ title: 'Agent Squad Gateway',
380
+ version: '0.1.0',
381
+ },
382
+ }, { cancellationSignal: request.signal });
383
+ this.validateInitialize(init, request);
384
+ let sessionId;
385
+ let configOptions;
386
+ if (requestedSessionId === null) {
387
+ const session = await context.request(acp.methods.agent.session.new, {
388
+ cwd: request.workspace,
389
+ mcpServers: [],
390
+ }, { cancellationSignal: request.signal });
391
+ sessionId = session.sessionId;
392
+ configOptions = session.configOptions ?? [];
393
+ }
394
+ else {
395
+ const session = await context.request(acp.methods.agent.session.resume, {
396
+ sessionId: requestedSessionId,
397
+ cwd: request.workspace,
398
+ mcpServers: [],
399
+ }, { cancellationSignal: request.signal });
400
+ sessionId = requestedSessionId;
401
+ configOptions = session.configOptions ?? [];
402
+ }
403
+ if (sessionId.length === 0)
404
+ throw new KimiAdapterError('adapter_protocol_error');
405
+ active.sessionId = sessionId;
406
+ queue.push({ type: 'session_started', nativeSessionId: sessionId });
407
+ observeConfig(configOptions, observation, false);
408
+ configOptions = await this.applyConfig(context, sessionId, configOptions, request);
409
+ observeConfig(configOptions, observation);
410
+ const prompt = await this.promptBlocks(request);
411
+ nativeStateAdvanced = true;
412
+ const response = await context.request(acp.methods.agent.session.prompt, {
413
+ sessionId,
414
+ prompt,
415
+ }, { cancellationSignal: request.signal });
416
+ if (protocolViolation)
417
+ throw new KimiAdapterError('adapter_protocol_error');
418
+ if (response.stopReason === 'cancelled' || active.cancellationRequested) {
419
+ turnTerminal = true;
420
+ queue.push({ type: 'cancelled' });
421
+ return;
422
+ }
423
+ if (response.stopReason !== 'end_turn') {
424
+ throw new KimiAdapterError('adapter_protocol_error');
425
+ }
426
+ turnTerminal = true;
427
+ queue.push({ type: 'completed' });
428
+ if (request.sessionMode === 'ephemeral'
429
+ && init.agentCapabilities?.sessionCapabilities?.delete) {
430
+ try {
431
+ await context.request(acp.methods.agent.session.delete, { sessionId });
432
+ }
433
+ catch {
434
+ // Ephemeral cleanup is best effort because Kimi may retain session history.
435
+ }
436
+ }
437
+ });
438
+ void producer.then(() => { producerSettled = true; queue.end(); }, async (error) => {
439
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, PROCESS_EXIT_CLASSIFICATION_MS));
440
+ producerSettled = true;
441
+ queue.fail(error);
442
+ });
443
+ void managed.exited.then((exit) => {
444
+ if (producerSettled || turnTerminal || active?.cancellationRequested)
445
+ return;
446
+ queue.fail(exit.code === 0
447
+ ? new KimiAdapterError('adapter_protocol_error')
448
+ : processError(managed, 'adapter_process_error'));
449
+ }, (error) => queue.fail(error));
450
+ for (;;) {
451
+ const event = await queue.take();
452
+ if (event === null)
453
+ break;
454
+ yield event;
455
+ }
456
+ }
457
+ catch (error) {
458
+ if (!protocolViolation && (request.signal.aborted || active?.cancellationRequested)) {
459
+ yield { type: 'cancelled' };
460
+ }
461
+ else {
462
+ yield failureEvent(error, nativeStateAdvanced);
463
+ }
464
+ }
465
+ finally {
466
+ request.signal.removeEventListener('abort', onAbort);
467
+ if (!producerSettled && active?.context && active.sessionId && !active.cancelSent) {
468
+ active.cancelSent = true;
469
+ try {
470
+ await active.context.notify(acp.methods.agent.session.cancel, {
471
+ sessionId: active.sessionId,
472
+ });
473
+ }
474
+ catch {
475
+ // The ACP connection may already be closed.
476
+ }
477
+ }
478
+ if (active && this.activeRuns.get(request.runId) === active) {
479
+ this.activeRuns.delete(request.runId);
480
+ }
481
+ if (managed)
482
+ await disposeQuietly(managed);
483
+ if (producer)
484
+ await producer.catch(() => undefined);
485
+ }
486
+ }
487
+ validateInitialize(init, request) {
488
+ if (init.protocolVersion !== acp.PROTOCOL_VERSION) {
489
+ throw new KimiAdapterError('adapter_capability_unsupported');
490
+ }
491
+ const sessions = init.agentCapabilities?.sessionCapabilities;
492
+ if (!sessions?.resume)
493
+ throw new KimiAdapterError('adapter_capability_unsupported');
494
+ if (init.agentCapabilities?.promptCapabilities?.image !== true) {
495
+ throw new KimiAdapterError('adapter_capability_unsupported');
496
+ }
497
+ }
498
+ async applyConfig(context, sessionId, options, request) {
499
+ let current = options;
500
+ if (request.model !== 'default') {
501
+ const model = findModelConfig(current);
502
+ if (!model)
503
+ throw new KimiAdapterError('adapter_capability_unsupported');
504
+ const response = await context.request(acp.methods.agent.session.setConfigOption, {
505
+ sessionId,
506
+ configId: model.id,
507
+ value: request.model,
508
+ }, { cancellationSignal: request.signal });
509
+ current = response.configOptions;
510
+ }
511
+ if (request.effort !== null && request.effort !== 'default') {
512
+ const effort = findEffortConfig(current);
513
+ if (!effort)
514
+ throw new KimiAdapterError('adapter_capability_unsupported');
515
+ const response = await context.request(acp.methods.agent.session.setConfigOption, {
516
+ sessionId,
517
+ configId: effort.id,
518
+ value: resolveConfigValue(effort, request.effort, 'effort'),
519
+ }, { cancellationSignal: request.signal });
520
+ current = response.configOptions;
521
+ }
522
+ return current;
523
+ }
524
+ async promptBlocks(request) {
525
+ const blocks = [{ type: 'text', text: renderPrompt(request) }];
526
+ for (const image of request.images ?? []) {
527
+ blocks.push({
528
+ type: 'image',
529
+ data: (await readFile(image.path)).toString('base64'),
530
+ mimeType: image.mediaType,
531
+ });
532
+ }
533
+ return blocks;
534
+ }
535
+ async staticMetadata() {
536
+ const versionOutput = await this.runCommand(['--version']);
537
+ const helpOutput = await this.runCommand(['--help']);
538
+ if (!/\bacp\b/i.test(helpOutput)) {
539
+ throw new KimiAdapterError('adapter_capability_unsupported');
540
+ }
541
+ const version = versionOutput.match(/\b\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\b/)?.[0];
542
+ if (!version)
543
+ throw new KimiAdapterError('adapter_protocol_error');
544
+ return version;
545
+ }
546
+ async runCommand(args) {
547
+ const managed = this.spawnProcess({
548
+ command: this.command,
549
+ args,
550
+ cwd: this.launchCwd,
551
+ env: { ...this.env },
552
+ });
553
+ managed.stdin.end();
554
+ let timeout;
555
+ try {
556
+ let output = '';
557
+ const collect = (async () => {
558
+ for await (const line of readBoundedLines(managed.stdout)) {
559
+ output += `${line}\n`;
560
+ if (Buffer.byteLength(output) > COMMAND_OUTPUT_LIMIT_BYTES) {
561
+ throw new KimiAdapterError('adapter_protocol_error');
562
+ }
563
+ }
564
+ })();
565
+ const timedOut = new Promise((_resolve, reject) => {
566
+ timeout = setTimeout(() => {
567
+ void managed.interrupt(0).then(() => reject(new KimiAdapterError('adapter_protocol_error')), () => reject(new KimiAdapterError('adapter_protocol_error')));
568
+ }, this.staticProbeTimeoutMs);
569
+ });
570
+ const [exit] = await Promise.race([
571
+ Promise.all([managed.exited, collect]),
572
+ timedOut,
573
+ ]);
574
+ const combined = [output.trim(), managed.stderrDiagnostic().trim()]
575
+ .filter(Boolean)
576
+ .join('\n');
577
+ if (exit.code !== 0 || combined.length === 0) {
578
+ throw processError(managed, 'adapter_process_error');
579
+ }
580
+ return combined;
581
+ }
582
+ finally {
583
+ if (timeout)
584
+ clearTimeout(timeout);
585
+ await disposeQuietly(managed);
586
+ }
587
+ }
588
+ async verifyConformance(request) {
589
+ const base = {
590
+ runId: `kimi-conformance-start-${Date.now()}`,
591
+ targetId: request.targetId,
592
+ model: request.model,
593
+ effort: request.effort,
594
+ workspace: request.workspace,
595
+ input: [{
596
+ role: 'user',
597
+ content: 'Return an assistant_text JSON object with a short English confirmation.',
598
+ }],
599
+ sessionMode: 'persistent',
600
+ runTimeoutMs: null,
601
+ outputSchema: CONFORMANCE_SCHEMA,
602
+ signal: request.signal,
603
+ };
604
+ const first = this.newObservation();
605
+ const nativeSessionId = await this.requireConformance(this.stream(base, null, first), first);
606
+ const resumed = this.newObservation();
607
+ await this.requireConformance(this.stream({
608
+ ...base,
609
+ runId: `kimi-conformance-resume-${Date.now()}`,
610
+ input: [{
611
+ role: 'user',
612
+ content: 'Return another assistant_text JSON object with a short English confirmation.',
613
+ }],
614
+ }, nativeSessionId, resumed), resumed);
615
+ for (const [id, label] of resumed.models)
616
+ first.models.set(id, label);
617
+ for (const [id, label] of resumed.efforts)
618
+ first.efforts.set(id, label);
619
+ return { models: conformanceModels(first, request.model) };
620
+ }
621
+ newObservation() {
622
+ return { textChunks: 0, models: new Map(), efforts: new Map() };
623
+ }
624
+ async requireConformance(events, observation) {
625
+ let sessionId = null;
626
+ let output = '';
627
+ let completed = false;
628
+ for await (const event of events) {
629
+ if (event.type === 'session_started')
630
+ sessionId = event.nativeSessionId;
631
+ else if (event.type === 'structured_delta')
632
+ output += event.delta;
633
+ else if (event.type === 'completed')
634
+ completed = true;
635
+ else if (event.type === 'failed' && event.code === 'adapter_capability_unsupported') {
636
+ throw new KimiAdapterError('adapter_capability_unsupported');
637
+ }
638
+ else
639
+ throw new KimiAdapterError('adapter_protocol_error');
640
+ }
641
+ if (!sessionId || !completed || observation.textChunks < 1) {
642
+ throw new KimiAdapterError('adapter_protocol_error');
643
+ }
644
+ let parsed;
645
+ try {
646
+ parsed = JSON.parse(output);
647
+ }
648
+ catch {
649
+ throw new KimiAdapterError('adapter_protocol_error');
650
+ }
651
+ if (!conformanceOutputSchema.safeParse(parsed).success) {
652
+ throw new KimiAdapterError('adapter_protocol_error');
653
+ }
654
+ return sessionId;
655
+ }
656
+ }
657
+ //# sourceMappingURL=adapter.js.map