@kosuke-ai/cli 6.8.0 → 6.10.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/dist/program.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { attachAgentCommands } from './commands/agent.js';
3
3
  import { attachAuthCommands } from './commands/auth.js';
4
+ import { attachIntegrationsCommands } from './commands/integrations.js';
4
5
  import { attachWatchCommand } from './commands/watch.js';
5
6
  import { operations } from './generated/commands/index.js';
6
7
  import { attachOperation } from './runtime/attach.js';
@@ -8,8 +9,8 @@ import { isCronToken, isLeadToken, isOrchestratorToken, isSandboxAgentToken, } f
8
9
  import { createRuntime, ExitSignal } from './runtime/runtime.js';
9
10
  import { version } from './runtime/version.js';
10
11
  /**
11
- * The command tree: `auth`, `agent` and `sessions watch` by hand, everything else
12
- * from the generated descriptors. Global flags are read before any subcommand runs, so the
12
+ * The command tree: `auth`, `agent`, `integrations list|describe|call` and `sessions watch` by
13
+ * hand, everything else from the generated descriptors. Global flags are read before any subcommand runs, so the
13
14
  * runtime the subcommands share is built once, from them.
14
15
  */
15
16
  export function createProgram(options = {}) {
@@ -55,6 +56,8 @@ export function createProgram(options = {}) {
55
56
  // member's own key and their Lead may mark Activity seen, so the others
56
57
  // read it with `--peek`.
57
58
  attachAgentCommands(program, lazy, options.agent);
59
+ // Beside the generated `integrations tools` and `execute`, which stay raw passthroughs.
60
+ attachIntegrationsCommands(program, lazy, options.integrations);
58
61
  // The job stream admits a member's API key only; it closes 4401 on every narrower token.
59
62
  if (narrowedTo === null)
60
63
  attachWatchCommand(program, lazy, options.watch);
@@ -99,6 +99,57 @@ function withTransportRetry(fetchImpl) {
99
99
  }
100
100
  };
101
101
  }
102
+ /** The most of one integration answer the CLI reads; its listings are bounded well below it. */
103
+ const MAX_INTEGRATION_RESPONSE_BYTES = 512 * 1024;
104
+ const INTEGRATIONS_PREFIX = '/api/agent-integrations/';
105
+ /** An integration answer past the cap; a call that got one may still have run. */
106
+ export class ResponseTooLarge extends Error {
107
+ constructor() {
108
+ super('Integration response was too large');
109
+ this.name = 'ResponseTooLarge';
110
+ }
111
+ }
112
+ /**
113
+ * Caps what an integration route can make the CLI buffer, error bodies included. The body is
114
+ * counted as it streams, so nothing past the cap is held or parsed. Other routes are untouched.
115
+ */
116
+ function withIntegrationCap(fetchImpl) {
117
+ return async (...args) => {
118
+ const response = await fetchImpl(...args);
119
+ const [input] = args;
120
+ try {
121
+ const href = input instanceof Request ? input.url : String(input);
122
+ if (!new URL(href, 'http://localhost').pathname.startsWith(INTEGRATIONS_PREFIX)) {
123
+ return response;
124
+ }
125
+ }
126
+ catch {
127
+ return response;
128
+ }
129
+ if (!response.body)
130
+ return response;
131
+ const declared = Number(response.headers.get('content-length'));
132
+ if (Number.isFinite(declared) && declared > MAX_INTEGRATION_RESPONSE_BYTES) {
133
+ await response.body.cancel().catch(() => undefined);
134
+ throw new ResponseTooLarge();
135
+ }
136
+ let total = 0;
137
+ const counted = response.body.pipeThrough(new TransformStream({
138
+ transform(chunk, controller) {
139
+ total += chunk.byteLength;
140
+ if (total > MAX_INTEGRATION_RESPONSE_BYTES)
141
+ controller.error(new ResponseTooLarge());
142
+ else
143
+ controller.enqueue(chunk);
144
+ },
145
+ }));
146
+ return new Response(counted, {
147
+ status: response.status,
148
+ statusText: response.statusText,
149
+ headers: response.headers,
150
+ });
151
+ };
152
+ }
102
153
  export function createApiClient({ apiUrl, apiKey, fetch }) {
103
154
  return createClient({
104
155
  baseUrl: apiUrl,
@@ -106,6 +157,6 @@ export function createApiClient({ apiUrl, apiKey, fetch }) {
106
157
  ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
107
158
  'User-Agent': `@kosuke-ai/cli/${version}`,
108
159
  },
109
- fetch: withTransportRetry(fetch ?? globalThis.fetch),
160
+ fetch: withIntegrationCap(withTransportRetry(fetch ?? globalThis.fetch)),
110
161
  });
111
162
  }
@@ -37,6 +37,13 @@ const LEAD_TOKEN_PREFIX = 'kosuke_lead_';
37
37
  export function isLeadToken(token) {
38
38
  return token?.startsWith(LEAD_TOKEN_PREFIX) ?? false;
39
39
  }
40
+ /** A credential that decides its own workspace: any of the four agent tokens. */
41
+ export function isTurnToken(token) {
42
+ return (isSandboxAgentToken(token ?? undefined) ||
43
+ isOrchestratorToken(token ?? undefined) ||
44
+ isCronToken(token ?? undefined) ||
45
+ isLeadToken(token ?? undefined));
46
+ }
40
47
  export function resolveCredentials(flags, env = process.env) {
41
48
  const stored = readCredentials(env);
42
49
  const apiKey = flags.apiKey
package/openapi.json CHANGED
@@ -141,6 +141,226 @@
141
141
  }
142
142
  }
143
143
  },
144
+ "/api/agent-integrations/execute": {
145
+ "post": {
146
+ "operationId": "integrationsExecute",
147
+ "summary": "Run one integration tool. An agent runs it for its own workspace, or on the member’s personal connection when that member started the turn; the Slack Bot’s agent runs only the workspace’s. With an API key, name a workspace you belong to: the call runs only on your own personal connections, and one on a workspace connection is refused. The answer names the connection it ran on, or lists the connections to choose from when the call needs one. A tool failure answers 200 with isError true. A Connector Token or a browser session is refused.",
148
+ "tags": ["integrations"],
149
+ "x-kosuke-command": ["integrations", "execute"],
150
+ "x-kosuke-sandbox-token": true,
151
+ "x-kosuke-orchestrator-token": true,
152
+ "x-kosuke-cron-token": true,
153
+ "x-kosuke-lead-token": true,
154
+ "parameters": [
155
+ {
156
+ "name": "workspace",
157
+ "in": "query",
158
+ "required": false,
159
+ "description": "The workspace the call is made in. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
160
+ "schema": {
161
+ "description": "The workspace the call is made in. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
162
+ "type": "string",
163
+ "minLength": 1,
164
+ "maxLength": 100
165
+ }
166
+ }
167
+ ],
168
+ "requestBody": {
169
+ "required": true,
170
+ "content": {
171
+ "application/json": {
172
+ "schema": {
173
+ "type": "object",
174
+ "properties": {
175
+ "name": {
176
+ "type": "string",
177
+ "minLength": 1,
178
+ "maxLength": 80,
179
+ "description": "The tool to run, as the tools list names it."
180
+ },
181
+ "arguments": {
182
+ "type": "object",
183
+ "propertyNames": {
184
+ "type": "string"
185
+ },
186
+ "additionalProperties": {},
187
+ "description": "The tool's arguments, as its input schema describes them."
188
+ },
189
+ "account": {
190
+ "description": "The connection to run on: its short id, its alias such as notion-personal, or scope:label. Needed when several can run the tool.",
191
+ "type": "string",
192
+ "minLength": 1,
193
+ "maxLength": 100
194
+ }
195
+ },
196
+ "required": ["name", "arguments"],
197
+ "additionalProperties": false
198
+ }
199
+ }
200
+ }
201
+ },
202
+ "responses": {
203
+ "2XX": {
204
+ "description": "Success envelope",
205
+ "content": {
206
+ "application/json": {
207
+ "schema": {
208
+ "$ref": "#/components/schemas/ApiSuccess"
209
+ }
210
+ }
211
+ }
212
+ },
213
+ "4XX": {
214
+ "description": "Error envelope",
215
+ "content": {
216
+ "application/json": {
217
+ "schema": {
218
+ "$ref": "#/components/schemas/ApiError"
219
+ }
220
+ }
221
+ }
222
+ },
223
+ "5XX": {
224
+ "description": "Error envelope",
225
+ "content": {
226
+ "application/json": {
227
+ "schema": {
228
+ "$ref": "#/components/schemas/ApiError"
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ }
235
+ },
236
+ "/api/agent-integrations/tools": {
237
+ "get": {
238
+ "operationId": "integrationsTools",
239
+ "summary": "List the integration tools you can run now, with the connections that run them. An agent reads its own workspace; the Slack Bot’s agent sees the workspace’s connections only. With an API key, name a workspace you belong to: it shows that workspace’s connections and your own personal ones. A Connector Token or a browser session is refused.",
240
+ "tags": ["integrations"],
241
+ "x-kosuke-command": ["integrations", "tools"],
242
+ "x-kosuke-sandbox-token": true,
243
+ "x-kosuke-orchestrator-token": true,
244
+ "x-kosuke-cron-token": true,
245
+ "x-kosuke-lead-token": true,
246
+ "parameters": [
247
+ {
248
+ "name": "workspace",
249
+ "in": "query",
250
+ "required": false,
251
+ "description": "The workspace to list for. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
252
+ "schema": {
253
+ "description": "The workspace to list for. Required with an API key, which is refused with 403 when you are not a member of it. An agent credential reaches its own workspace and refuses another.",
254
+ "type": "string",
255
+ "minLength": 1,
256
+ "maxLength": 100
257
+ }
258
+ },
259
+ {
260
+ "name": "view",
261
+ "in": "query",
262
+ "required": false,
263
+ "description": "overview: the connections per toolkit with tool counts, and the matching tools once toolkit or search is set. summary: tool names and short descriptions, cut at 300. Without it, full definitions cut at a size budget, which is what older clients read.",
264
+ "schema": {
265
+ "description": "overview: the connections per toolkit with tool counts, and the matching tools once toolkit or search is set. summary: tool names and short descriptions, cut at 300. Without it, full definitions cut at a size budget, which is what older clients read.",
266
+ "type": "string",
267
+ "enum": ["summary", "overview"]
268
+ }
269
+ },
270
+ {
271
+ "name": "toolkit",
272
+ "in": "query",
273
+ "required": false,
274
+ "description": "Only this toolkit, by slug, such as gmail.",
275
+ "schema": {
276
+ "description": "Only this toolkit, by slug, such as gmail.",
277
+ "type": "string",
278
+ "pattern": "^[a-z0-9_]{1,64}$"
279
+ }
280
+ },
281
+ {
282
+ "name": "search",
283
+ "in": "query",
284
+ "required": false,
285
+ "description": "Only tools whose name, description or toolkit contains this text, in any case.",
286
+ "schema": {
287
+ "description": "Only tools whose name, description or toolkit contains this text, in any case.",
288
+ "type": "string",
289
+ "minLength": 1,
290
+ "maxLength": 100
291
+ }
292
+ },
293
+ {
294
+ "name": "name",
295
+ "in": "query",
296
+ "required": false,
297
+ "description": "One tool by exact name, with its full input schema. 404 when you are not offered it.",
298
+ "schema": {
299
+ "description": "One tool by exact name, with its full input schema. 404 when you are not offered it.",
300
+ "type": "string",
301
+ "minLength": 1,
302
+ "maxLength": 80
303
+ }
304
+ },
305
+ {
306
+ "name": "account",
307
+ "in": "query",
308
+ "required": false,
309
+ "description": "Only one connection: its short id, its alias such as notion-personal, or scope:label. Needed for a name that several connections can run.",
310
+ "schema": {
311
+ "description": "Only one connection: its short id, its alias such as notion-personal, or scope:label. Needed for a name that several connections can run.",
312
+ "type": "string",
313
+ "minLength": 1,
314
+ "maxLength": 100
315
+ }
316
+ },
317
+ {
318
+ "name": "limit",
319
+ "in": "query",
320
+ "required": false,
321
+ "description": "With view overview, how many tools to list when toolkit or search is set. 40 by default.",
322
+ "schema": {
323
+ "description": "With view overview, how many tools to list when toolkit or search is set. 40 by default.",
324
+ "type": "integer",
325
+ "minimum": 1,
326
+ "maximum": 100
327
+ }
328
+ }
329
+ ],
330
+ "responses": {
331
+ "2XX": {
332
+ "description": "Success envelope",
333
+ "content": {
334
+ "application/json": {
335
+ "schema": {
336
+ "$ref": "#/components/schemas/ApiSuccess"
337
+ }
338
+ }
339
+ }
340
+ },
341
+ "4XX": {
342
+ "description": "Error envelope",
343
+ "content": {
344
+ "application/json": {
345
+ "schema": {
346
+ "$ref": "#/components/schemas/ApiError"
347
+ }
348
+ }
349
+ }
350
+ },
351
+ "5XX": {
352
+ "description": "Error envelope",
353
+ "content": {
354
+ "application/json": {
355
+ "schema": {
356
+ "$ref": "#/components/schemas/ApiError"
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+ },
144
364
  "/api/auth/github/repositories": {
145
365
  "get": {
146
366
  "operationId": "githubRepositories",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kosuke-ai/cli",
3
- "version": "6.8.0",
3
+ "version": "6.10.0",
4
4
  "description": "Kosuke platform CLI. The client customers install to drive app.kosuke.ai from their own machine.",
5
5
  "license": "MIT",
6
6
  "repository": {