@2kw/ai 8.0.2-dev.2 → 8.0.3

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.
@@ -9,7 +9,6 @@ import { buildRunEnvelope, EXIT_CODES, MODE_OPTION_HELP, modeItem, parseModeOpti
9
9
  import { continueWithDecisions, decisionsFromAnswers, fetchPendingApprovals, parseToolAnswers, planContinuation, } from "../lib/agent-decide.js";
10
10
  import { PromptAbortedError, promptApprovals, readlineAsk } from "../lib/approval-prompt.js";
11
11
  import { withModelOverride } from "../lib/agent-models.js";
12
- import { postResponse } from "../lib/response-stream.js";
13
12
  import { readStdinInput } from "./ai.js";
14
13
  export function resolveRunInput(arg, inputFile, readStdin = readStdinInput) {
15
14
  const text = readRunInput(arg, inputFile, readStdin);
@@ -147,7 +146,7 @@ export function makeAgentRunCommand() {
147
146
  const body = {
148
147
  model: `agent/${agentRef}`,
149
148
  input: mode ? [...turn, modeItem(mode)] : text ?? [],
150
- stream: true,
149
+ stream: false,
151
150
  };
152
151
  if (opts.conversation)
153
152
  body.conversation = opts.conversation;
@@ -155,17 +154,9 @@ export function makeAgentRunCommand() {
155
154
  body.previous_response_id = opts.continue;
156
155
  let data;
157
156
  try {
158
- data = await withSpinner("Running agent...", (spinner) => postResponse(client, body, (started) => {
159
- // Printed at once (#1729), so a run cut off later can still be found.
160
- if (!started.conversation?.id)
161
- return;
162
- // ora's render() writes a frame even off a TTY, where it never spins.
163
- if (spinner.isSpinning)
164
- spinner.clear();
165
- console.error(`Conversation: ${started.conversation.id}`);
166
- if (spinner.isSpinning)
167
- spinner.render();
168
- }));
157
+ // The generated body type does not model a bare-string input, hence the cast.
158
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
159
+ ({ data } = await withSpinner("Running agent...", () => client.POST("/v1/responses", { body })));
169
160
  }
170
161
  catch (err) {
171
162
  throw await withAgentSuggestions(client, agentRef, err);
@@ -7,8 +7,7 @@ export function makeResponsesCommand() {
7
7
  cmd
8
8
  .command("get")
9
9
  .description("Read a background response as it stands now: queued, in_progress, requires_action, completed, " +
10
- "incomplete, failed or cancelled. Only background responses can be read by id: the result of " +
11
- "`agents run` is read with `conversations items list <conversationId>`.")
10
+ "incomplete, failed or cancelled. Only background responses can be read by id.")
12
11
  .argument("<responseId>", "Response ID")
13
12
  .action(async (responseId, _opts, command) => {
14
13
  await runAction(command, async () => {
@@ -2,7 +2,6 @@ import { readFileSync } from "node:fs";
2
2
  import { modeItem } from "./agent-run.js";
3
3
  import { CliUsageError } from "./errors.js";
4
4
  import { paginationParams } from "./pagination.js";
5
- import { postResponse } from "./response-stream.js";
6
5
  const defaultIo = {
7
6
  readFile: (path) => readFileSync(path, "utf-8"),
8
7
  // Not readStdinInput: that one trims, and a tool result is sent as read (P5).
@@ -216,10 +215,12 @@ export async function continueWithDecisions(client, agentId, responseId, decisio
216
215
  const body = {
217
216
  model: `agent/${agentId}${label ? `@${label}` : ""}${model ? `#${model}` : ""}`,
218
217
  previous_response_id: responseId,
219
- stream: true,
218
+ stream: false,
220
219
  input: mode ? [...items, modeItem(mode)] : items,
221
220
  };
222
- // Streamed for the reason a run is (#1729): the rest of the turn can outlast a proxy's timeout.
223
- return postResponse(client, body);
221
+ // The generated body type does not model backbone: extension items, hence the cast.
222
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
+ const { data } = await client.POST("/v1/responses", { body });
224
+ return data;
224
225
  }
225
226
  //# sourceMappingURL=agent-decide.js.map
@@ -1,4 +1,3 @@
1
- import { type Ora } from "ora";
2
1
  import type { Command } from "commander";
3
2
  type AnyRecord = Record<string, any>;
4
3
  /**
@@ -25,6 +24,6 @@ export declare function formatSuccess(message: string, command: Command): void;
25
24
  /**
26
25
  * Run an async action with a spinner.
27
26
  */
28
- export declare function withSpinner<T>(text: string, action: (spinner: Ora) => Promise<T>): Promise<T>;
27
+ export declare function withSpinner<T>(text: string, action: () => Promise<T>): Promise<T>;
29
28
  export {};
30
29
  //# sourceMappingURL=output.d.ts.map
@@ -88,7 +88,7 @@ export function formatSuccess(message, command) {
88
88
  export async function withSpinner(text, action) {
89
89
  const spinner = ora(text).start();
90
90
  try {
91
- const result = await action(spinner);
91
+ const result = await action();
92
92
  spinner.succeed();
93
93
  return result;
94
94
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "8.0.2-dev.2",
3
+ "version": "8.0.3",
4
4
  "description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,14 +0,0 @@
1
- import type { ApiClient } from "./agent-lookup.js";
2
- import type { ResponsesResult } from "./agent-run.js";
3
- /**
4
- * POSTs an agent turn to `/v1/responses` and returns its terminal response, as `stream: false` would (#1729).
5
- * The turn is streamed: a proxy in front of the API cuts a request that answers nothing for 100 s (HTTP 524),
6
- * and a stream's events and keepalives keep it open however long the run takes. A model that cannot stream
7
- * is refused before any work (`stream_not_supported`), so that turn is sent again without a stream.
8
- *
9
- * `onStart` gets the response as `response.created` names it, before the run works. Once the conversation is
10
- * known, a cut stream or Ctrl+C names it and the command that reads the run's result: a synchronous
11
- * response cannot be read by id afterwards (`GET /v1/responses/{id}` reads background responses only).
12
- */
13
- export declare function postResponse(client: ApiClient, body: Record<string, unknown>, onStart?: (started: ResponsesResult) => void): Promise<ResponsesResult>;
14
- //# sourceMappingURL=response-stream.d.ts.map
@@ -1,120 +0,0 @@
1
- import { BackboneApiError } from "./errors.js";
2
- /**
3
- * POSTs an agent turn to `/v1/responses` and returns its terminal response, as `stream: false` would (#1729).
4
- * The turn is streamed: a proxy in front of the API cuts a request that answers nothing for 100 s (HTTP 524),
5
- * and a stream's events and keepalives keep it open however long the run takes. A model that cannot stream
6
- * is refused before any work (`stream_not_supported`), so that turn is sent again without a stream.
7
- *
8
- * `onStart` gets the response as `response.created` names it, before the run works. Once the conversation is
9
- * known, a cut stream or Ctrl+C names it and the command that reads the run's result: a synchronous
10
- * response cannot be read by id afterwards (`GET /v1/responses/{id}` reads background responses only).
11
- */
12
- export async function postResponse(client, body, onStart) {
13
- let response;
14
- try {
15
- // The generated body type does not model a bare-string input or backbone: items, hence the cast.
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- ({ response } = await client.POST("/v1/responses", { body: { ...body, stream: true }, parseAs: "stream" }));
18
- }
19
- catch (err) {
20
- if (!(err instanceof BackboneApiError && err.code === "stream_not_supported"))
21
- throw err;
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- const { data } = await client.POST("/v1/responses", { body: { ...body, stream: false } });
24
- return data;
25
- }
26
- if (!response.body || !response.headers.get("content-type")?.includes("text/event-stream")) {
27
- return (await response.json());
28
- }
29
- return readResponseStream(response.body, onStart);
30
- }
31
- /** How to read a run whose stream was lost: the conversation keeps every item the run wrote. */
32
- function readHint(started) {
33
- const conversation = started?.conversation?.id;
34
- return conversation
35
- ? ` The run goes on on the server; read its result with: 2kw conversations items list ${conversation}`
36
- : "";
37
- }
38
- /** Reads Open Responses server-sent events up to the terminal one; comments (keepalives) are skipped. */
39
- async function readResponseStream(body, onStart) {
40
- let started;
41
- let error;
42
- const failed = () => new BackboneApiError({
43
- status: 500,
44
- error: error?.code ?? "run_failed",
45
- code: error?.code ?? undefined,
46
- message: error?.message ?? "The response failed.",
47
- timestamp: new Date().toISOString(),
48
- });
49
- const onInterrupt = () => {
50
- console.error(`Interrupted.${readHint(started)}`);
51
- process.exit(130);
52
- };
53
- const handle = (frame) => {
54
- const data = frame
55
- .split(/\r?\n/)
56
- .filter((line) => line.startsWith("data:"))
57
- .map((line) => line.slice(5).trimStart())
58
- .join("\n");
59
- if (!data || data === "[DONE]")
60
- return undefined;
61
- let event;
62
- try {
63
- event = JSON.parse(data);
64
- }
65
- catch {
66
- return undefined; // a garbled frame is skipped: the run goes on, and its terminal event still ends it
67
- }
68
- switch (event.type) {
69
- case "response.created":
70
- started = event.response;
71
- if (started?.conversation?.id)
72
- process.once("SIGINT", onInterrupt);
73
- onStart?.(event.response);
74
- return undefined;
75
- case "error":
76
- error = event;
77
- return undefined;
78
- case "response.completed":
79
- case "response.incomplete":
80
- return event.response;
81
- case "response.failed":
82
- throw failed();
83
- default:
84
- return undefined;
85
- }
86
- };
87
- const decoder = new TextDecoder();
88
- let buffer = "";
89
- const reader = body.getReader();
90
- try {
91
- for (;;) {
92
- let chunk;
93
- try {
94
- chunk = await reader.read();
95
- }
96
- catch (err) {
97
- throw new Error(`The connection closed before the run finished (${err.message}).${readHint(started)}`);
98
- }
99
- if (chunk.done)
100
- break;
101
- buffer += decoder.decode(chunk.value, { stream: true });
102
- let end;
103
- while ((end = /\r?\n\r?\n/.exec(buffer))) {
104
- const frame = buffer.slice(0, end.index);
105
- buffer = buffer.slice(end.index + end[0].length);
106
- const terminal = handle(frame);
107
- if (terminal)
108
- return terminal;
109
- }
110
- }
111
- }
112
- finally {
113
- process.removeListener("SIGINT", onInterrupt);
114
- await reader.cancel().catch(() => { });
115
- }
116
- if (error)
117
- throw failed(); // an error event whose response.failed never arrived
118
- throw new Error(`The response stream ended before the run finished.${readHint(started)}`);
119
- }
120
- //# sourceMappingURL=response-stream.js.map