@tyvm/knowhow 0.0.143 → 0.0.145
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/bin/knowhow.js +22 -20
- package/package.json +1 -1
- package/scripts/migrate-usage-json.js +147 -0
- package/src/agents/base/base.ts +65 -10
- package/src/agents/tools/patch.ts +84 -2
- package/src/agents/tools/textSearch.ts +98 -3
- package/src/chat/modules/AgentModule.ts +129 -6
- package/src/chat/modules/SessionsModule.ts +6 -0
- package/src/chat/types.ts +14 -0
- package/src/chat.ts +11 -0
- package/src/cli.ts +2 -0
- package/src/clients/anthropic.ts +56 -1
- package/src/clients/contextLimits.ts +40 -0
- package/src/clients/gemini.ts +59 -5
- package/src/clients/http.ts +85 -4
- package/src/clients/index.ts +4 -0
- package/src/clients/openai.ts +179 -31
- package/src/clients/pricing/fireworks.ts +50 -1
- package/src/clients/pricing/index.ts +1 -1
- package/src/clients/pricing/models.ts +3 -1
- package/src/clients/pricing/openai.ts +2 -2
- package/src/clients/pricing/qwen.ts +80 -0
- package/src/clients/qwen.ts +16 -0
- package/src/clients/types.ts +69 -1
- package/src/clients/xai.ts +73 -4
- package/src/commands/agent.ts +20 -1
- package/src/commands/sandbox.ts +139 -114
- package/src/index.ts +9 -0
- package/src/processors/Base64ImageDetector.ts +46 -7
- package/src/processors/GemmaToolProcessor.ts +163 -0
- package/src/processors/index.ts +1 -0
- package/src/services/AgentSyncFs.ts +37 -0
- package/src/services/SessionManager.ts +14 -0
- package/src/services/Tools.ts +3 -0
- package/src/services/modules/computerUse.ts +216 -0
- package/src/services/modules/index.ts +47 -9
- package/src/services/modules/types.ts +23 -1
- package/src/types.ts +3 -0
- package/tests/clients/contextLimits.test.ts +4 -0
- package/tests/clients/openai.images.test.ts +105 -0
- package/tests/clients/pricing.test.ts +6 -0
- package/tests/compressor/usageBase64.json +159 -0
- package/tests/compressor/usageBase64.test.ts +175 -0
- package/tests/patching/fixtures/transcript-mangles/agent-sync-fs-nested-catch.json +9 -0
- package/tests/patching/fixtures/transcript-mangles/automation-functions-crossed.json +9 -0
- package/tests/patching/fixtures/transcript-mangles/fruit-ninja-confirmed-retirement.json +8 -0
- package/tests/patching/patchFileOutput.test.ts +112 -1
- package/tests/patching/transcriptMangleRegression.test.ts +125 -0
- package/tests/processors/Base64ImageDetector.test.ts +36 -1
- package/tests/unit/commands/sandbox.test.ts +350 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +8 -1
- package/ts_build/src/agents/base/base.js +37 -3
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/patch.d.ts +1 -0
- package/ts_build/src/agents/tools/patch.js +35 -2
- package/ts_build/src/agents/tools/patch.js.map +1 -1
- package/ts_build/src/agents/tools/textSearch.js +56 -2
- package/ts_build/src/agents/tools/textSearch.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +14 -1
- package/ts_build/src/chat/modules/AgentModule.js +73 -5
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +4 -0
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/types.d.ts +11 -0
- package/ts_build/src/chat.js +7 -0
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/cli.d.ts +2 -1
- package/ts_build/src/cli.js +1 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +28 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +25 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/gemini.js +48 -4
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.d.ts +2 -0
- package/ts_build/src/clients/http.js +66 -9
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +1 -0
- package/ts_build/src/clients/index.js +4 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +6 -1
- package/ts_build/src/clients/openai.js +131 -33
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +4 -1
- package/ts_build/src/clients/pricing/fireworks.js +33 -1
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +1 -1
- package/ts_build/src/clients/pricing/index.js +6 -2
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/models.d.ts +2 -1
- package/ts_build/src/clients/pricing/models.js +4 -1
- package/ts_build/src/clients/pricing/models.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +2 -2
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/qwen.d.ts +5 -0
- package/ts_build/src/clients/pricing/qwen.js +30 -0
- package/ts_build/src/clients/pricing/qwen.js.map +1 -0
- package/ts_build/src/clients/qwen.d.ts +4 -0
- package/ts_build/src/clients/qwen.js +15 -0
- package/ts_build/src/clients/qwen.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +12 -0
- package/ts_build/src/clients/xai.d.ts +1 -0
- package/ts_build/src/clients/xai.js +56 -9
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/agent.js +10 -1
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/sandbox.js +79 -80
- package/ts_build/src/commands/sandbox.js.map +1 -1
- package/ts_build/src/index.d.ts +2 -0
- package/ts_build/src/index.js +1 -0
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.js +22 -3
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/processors/GemmaToolProcessor.d.ts +14 -0
- package/ts_build/src/processors/GemmaToolProcessor.js +97 -0
- package/ts_build/src/processors/GemmaToolProcessor.js.map +1 -0
- package/ts_build/src/processors/index.d.ts +1 -0
- package/ts_build/src/processors/index.js +3 -1
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.js +17 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/SessionManager.js +12 -0
- package/ts_build/src/services/SessionManager.js.map +1 -1
- package/ts_build/src/services/Tools.d.ts +2 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/modules/computerUse.d.ts +163 -0
- package/ts_build/src/services/modules/computerUse.js +3 -0
- package/ts_build/src/services/modules/computerUse.js.map +1 -0
- package/ts_build/src/services/modules/index.d.ts +1 -0
- package/ts_build/src/services/modules/index.js +23 -6
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +4 -0
- package/ts_build/src/types.d.ts +3 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/workers/tools/index.d.ts +1 -1
- package/ts_build/tests/clients/contextLimits.test.js +2 -0
- package/ts_build/tests/clients/contextLimits.test.js.map +1 -1
- package/ts_build/tests/clients/openai.images.test.d.ts +1 -0
- package/ts_build/tests/clients/openai.images.test.js +94 -0
- package/ts_build/tests/clients/openai.images.test.js.map +1 -0
- package/ts_build/tests/clients/pricing.test.js +4 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/compressor/usageBase64.test.d.ts +1 -0
- package/ts_build/tests/compressor/usageBase64.test.js +109 -0
- package/ts_build/tests/compressor/usageBase64.test.js.map +1 -0
- package/ts_build/tests/patching/patchFileOutput.test.js +82 -0
- package/ts_build/tests/patching/patchFileOutput.test.js.map +1 -1
- package/ts_build/tests/patching/transcriptMangleRegression.test.d.ts +1 -0
- package/ts_build/tests/patching/transcriptMangleRegression.test.js +104 -0
- package/ts_build/tests/patching/transcriptMangleRegression.test.js.map +1 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js +24 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/ts_build/tests/unit/commands/sandbox.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/sandbox.test.js +258 -0
- package/ts_build/tests/unit/commands/sandbox.test.js.map +1 -0
- package/x-home.png +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { HttpClient } from "./http";
|
|
2
|
+
import { QwenTextPricing } from "./pricing/qwen";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* QWEN Cloud client — OpenAI-compatible API
|
|
6
|
+
* https://dashscope-intl.aliyuncs.com/compatible-mode/v1
|
|
7
|
+
* Alibaba Cloud's Qwen model family via the international DashScope endpoint.
|
|
8
|
+
* Set env var QWEN_CLOUD_API_KEY to enable.
|
|
9
|
+
*/
|
|
10
|
+
export class GenericQwenClient extends HttpClient {
|
|
11
|
+
constructor(apiKey = process.env.QWEN_CLOUD_API_KEY) {
|
|
12
|
+
super("https://dashscope-intl.aliyuncs.com/compatible-mode/v1");
|
|
13
|
+
if (apiKey) this.setJwt(apiKey);
|
|
14
|
+
this.setPrices(QwenTextPricing);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/clients/types.ts
CHANGED
|
@@ -8,7 +8,10 @@ export type ModelModality =
|
|
|
8
8
|
|
|
9
9
|
export type MessageContent =
|
|
10
10
|
| { type: "text"; text: string }
|
|
11
|
-
| {
|
|
11
|
+
| {
|
|
12
|
+
type: "image_url";
|
|
13
|
+
image_url: { url: string; detail?: "auto" | "low" | "high" };
|
|
14
|
+
}
|
|
12
15
|
| { type: "audio_url"; audio_url: { url: string } }
|
|
13
16
|
| { type: "video_url"; video_url: { url: string } };
|
|
14
17
|
|
|
@@ -23,6 +26,50 @@ export interface Message {
|
|
|
23
26
|
|
|
24
27
|
export interface OutputMessage extends Message {
|
|
25
28
|
content?: string | null;
|
|
29
|
+
/**
|
|
30
|
+
* The model's reasoning/thinking content, when the provider exposes it.
|
|
31
|
+
* - OpenAI Responses API: a human-readable summary of the model's reasoning
|
|
32
|
+
* (populated when reasoning_summary is requested).
|
|
33
|
+
*/
|
|
34
|
+
reasoning_summary?: string;
|
|
35
|
+
/**
|
|
36
|
+
* OpenAI Responses API only (internal): the raw reasoning output items
|
|
37
|
+
* (carrying encrypted_content) captured from a stateless response. These are
|
|
38
|
+
* re-injected into the next request's input to preserve the reasoning chain.
|
|
39
|
+
* Not intended for display.
|
|
40
|
+
*
|
|
41
|
+
* @deprecated Prefer the provider-agnostic `_reasoning_details` slot. This is
|
|
42
|
+
* retained for backward compatibility with the OpenAI client only.
|
|
43
|
+
*/
|
|
44
|
+
_reasoning_items?: any[];
|
|
45
|
+
/**
|
|
46
|
+
* Provider-agnostic (internal): an opaque, provider-tagged carrier for the
|
|
47
|
+
* model's reasoning artifact that must be round-tripped verbatim on the next
|
|
48
|
+
* tool-use turn to preserve reasoning continuity. Each provider stashes its
|
|
49
|
+
* own native artifact here and re-injects it only when the `provider` tag
|
|
50
|
+
* matches, so provider-specific payload never leaks into the generic
|
|
51
|
+
* (OpenAI-format) message shape.
|
|
52
|
+
*
|
|
53
|
+
* - openai / xai: Responses API `reasoning` output items (encrypted_content)
|
|
54
|
+
* - anthropic: `thinking` / `redacted_thinking` blocks (with signature/data)
|
|
55
|
+
* - gemini: `thoughtSignature` strings keyed by tool-call id (+ thought parts)
|
|
56
|
+
*
|
|
57
|
+
* Not intended for display.
|
|
58
|
+
*/
|
|
59
|
+
_reasoning_details?: ReasoningDetails;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Opaque, provider-tagged reasoning artifact. The `items` are provider-native
|
|
64
|
+
* and only meaningful to the client whose id matches `provider`; any other
|
|
65
|
+
* client must ignore them (they are never mapped into a foreign provider's
|
|
66
|
+
* request payload).
|
|
67
|
+
*/
|
|
68
|
+
export interface ReasoningDetails {
|
|
69
|
+
/** The provider that produced these items, e.g. "openai" | "xai" | "anthropic" | "gemini". */
|
|
70
|
+
provider: string;
|
|
71
|
+
/** Provider-native reasoning items to round-trip verbatim on the next turn. */
|
|
72
|
+
items: any[];
|
|
26
73
|
}
|
|
27
74
|
|
|
28
75
|
export interface ToolProp {
|
|
@@ -30,6 +77,7 @@ export interface ToolProp {
|
|
|
30
77
|
description?: string;
|
|
31
78
|
properties?: { [key: string]: ToolProp };
|
|
32
79
|
items?: ToolProp;
|
|
80
|
+
enum?: any[];
|
|
33
81
|
}
|
|
34
82
|
|
|
35
83
|
export interface Tool {
|
|
@@ -114,6 +162,20 @@ export interface CompletionOptions extends RetryOptions {
|
|
|
114
162
|
* Defaults to false (no summary displayed).
|
|
115
163
|
*/
|
|
116
164
|
reasoning_summary?: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* OpenAI Responses API only: when true, OpenAI stores the response server-side
|
|
167
|
+
* so subsequent turns can reference it via `previous_response_id` instead of
|
|
168
|
+
* resending the full reasoning/message history. Defaults to false (stateless).
|
|
169
|
+
* When false, the client automatically requests `reasoning.encrypted_content`
|
|
170
|
+
* and threads the reasoning items back in the next request's input.
|
|
171
|
+
*/
|
|
172
|
+
store?: boolean;
|
|
173
|
+
/**
|
|
174
|
+
* OpenAI Responses API only: the id of a previously-stored response to continue
|
|
175
|
+
* from. Only meaningful when `store` was true on the prior request. When set,
|
|
176
|
+
* the model keeps its prior chain-of-thought instead of re-deriving it.
|
|
177
|
+
*/
|
|
178
|
+
previous_response_id?: string;
|
|
117
179
|
/**
|
|
118
180
|
* When true, hints to the client that this task is long-running and it should
|
|
119
181
|
* use a long-TTL cache where available.
|
|
@@ -165,6 +227,12 @@ export interface CompletionResponse {
|
|
|
165
227
|
model: string;
|
|
166
228
|
usage: TokenUsage | undefined;
|
|
167
229
|
usd_cost?: number;
|
|
230
|
+
/**
|
|
231
|
+
* OpenAI Responses API only: the id of this response. When `store` was true,
|
|
232
|
+
* this can be passed as `previous_response_id` on the next request to continue
|
|
233
|
+
* the reasoning chain without resending history.
|
|
234
|
+
*/
|
|
235
|
+
response_id?: string;
|
|
168
236
|
}
|
|
169
237
|
|
|
170
238
|
/** A single chunk yielded by a streaming completion. */
|
package/src/clients/xai.ts
CHANGED
|
@@ -116,6 +116,20 @@ export class GenericXAIClient implements GenericClient {
|
|
|
116
116
|
* Used for grok-4.20 reasoning/non-reasoning and multi-agent models.
|
|
117
117
|
* Translates Chat Completions message format to Responses API format.
|
|
118
118
|
*/
|
|
119
|
+
/**
|
|
120
|
+
* Resolve the xai-native reasoning items (Responses API `reasoning` items
|
|
121
|
+
* with encrypted_content) to re-inject for an assistant message, reading the
|
|
122
|
+
* provider-agnostic `_reasoning_details` slot only when it was produced by
|
|
123
|
+
* this provider.
|
|
124
|
+
*/
|
|
125
|
+
private getReasoningItems(msg: any): any[] {
|
|
126
|
+
const details = msg?._reasoning_details;
|
|
127
|
+
if (details && details.provider === "xai" && Array.isArray(details.items)) {
|
|
128
|
+
return details.items;
|
|
129
|
+
}
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
119
133
|
async createChatResponse(
|
|
120
134
|
options: CompletionOptions
|
|
121
135
|
): Promise<CompletionResponse> {
|
|
@@ -133,22 +147,46 @@ export class GenericXAIClient implements GenericClient {
|
|
|
133
147
|
.trim() || undefined;
|
|
134
148
|
|
|
135
149
|
// Convert chat messages to Responses API input items
|
|
150
|
+
// Responses API hard limit: 10,485,760 chars per function_call_output.output
|
|
151
|
+
const MAX_TOOL_OUTPUT = 10_485_000;
|
|
152
|
+
const truncateToolOutput = (s: string) =>
|
|
153
|
+
s.length > MAX_TOOL_OUTPUT
|
|
154
|
+
? s.slice(0, MAX_TOOL_OUTPUT) + "\n\n[TRUNCATED: output exceeded 10MB API limit]"
|
|
155
|
+
: s;
|
|
136
156
|
const input: any[] = nonSystemMessages.map((msg) => {
|
|
137
157
|
if (msg.role === "tool") {
|
|
138
158
|
return {
|
|
139
159
|
type: "function_call_output",
|
|
140
160
|
call_id: msg.tool_call_id,
|
|
141
|
-
output: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
161
|
+
output: truncateToolOutput(typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)),
|
|
142
162
|
};
|
|
143
163
|
}
|
|
144
164
|
if (msg.role === "assistant" && msg.tool_calls?.length) {
|
|
145
|
-
|
|
165
|
+
// Re-inject any captured reasoning items (encrypted_content) BEFORE the
|
|
166
|
+
// function_call items so the model keeps its chain-of-thought instead of
|
|
167
|
+
// re-deriving it from scratch each turn.
|
|
168
|
+
const reasoningItems = this.getReasoningItems(msg);
|
|
169
|
+
return [
|
|
170
|
+
...reasoningItems,
|
|
171
|
+
...msg.tool_calls.map((tc) => ({
|
|
146
172
|
type: "function_call",
|
|
147
173
|
id: tc.id.startsWith("fc") ? tc.id : `fc_${tc.id}`,
|
|
148
174
|
call_id: tc.id,
|
|
149
175
|
name: tc.function.name,
|
|
150
176
|
arguments: tc.function.arguments,
|
|
151
|
-
|
|
177
|
+
})),
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
if (msg.role === "assistant" && this.getReasoningItems(msg).length) {
|
|
181
|
+
// assistant message (no tool calls) that carried reasoning items →
|
|
182
|
+
// re-inject reasoning items, then the visible message content.
|
|
183
|
+
return [
|
|
184
|
+
...this.getReasoningItems(msg),
|
|
185
|
+
{
|
|
186
|
+
role: msg.role,
|
|
187
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
188
|
+
},
|
|
189
|
+
];
|
|
152
190
|
}
|
|
153
191
|
return {
|
|
154
192
|
role: msg.role,
|
|
@@ -182,7 +220,18 @@ export class GenericXAIClient implements GenericClient {
|
|
|
182
220
|
...(options.max_tokens && { max_output_tokens: Math.max(options.max_tokens, 16_000) }),
|
|
183
221
|
...(reasoningEffort && { reasoning: { effort: reasoningEffort } }),
|
|
184
222
|
...(tools?.length && { tools, tool_choice: "auto" }),
|
|
185
|
-
|
|
223
|
+
// Reasoning persistence (same construct as OpenAI's Responses API):
|
|
224
|
+
// - store === true → xAI persists the response; next turn passes
|
|
225
|
+
// previous_response_id to continue the reasoning chain server-side.
|
|
226
|
+
// - store !== true → stateless; request encrypted reasoning content so
|
|
227
|
+
// we can thread the reasoning items back into the next request ourselves.
|
|
228
|
+
store: options.store === true,
|
|
229
|
+
...(options.previous_response_id && {
|
|
230
|
+
previous_response_id: options.previous_response_id,
|
|
231
|
+
}),
|
|
232
|
+
...(options.store !== true && reasoningEffort && {
|
|
233
|
+
include: ["reasoning.encrypted_content"],
|
|
234
|
+
}),
|
|
186
235
|
};
|
|
187
236
|
|
|
188
237
|
const response = await fetch("https://api.x.ai/v1/responses", {
|
|
@@ -218,6 +267,10 @@ export class GenericXAIClient implements GenericClient {
|
|
|
218
267
|
// Collect text content and tool calls from output items
|
|
219
268
|
let textContent: string | null = null;
|
|
220
269
|
const toolCalls: any[] = [];
|
|
270
|
+
// Reasoning items (with encrypted_content) to thread back into the next
|
|
271
|
+
// turn, plus a human-readable summary of the model's thinking (if present).
|
|
272
|
+
const reasoningItems: any[] = [];
|
|
273
|
+
let reasoningSummary: string | null = null;
|
|
221
274
|
|
|
222
275
|
for (const item of data.output ?? []) {
|
|
223
276
|
if (item.type === "message") {
|
|
@@ -226,6 +279,17 @@ export class GenericXAIClient implements GenericClient {
|
|
|
226
279
|
textContent = (textContent ?? "") + part.text;
|
|
227
280
|
}
|
|
228
281
|
}
|
|
282
|
+
} else if (item.type === "reasoning") {
|
|
283
|
+
// Keep the raw reasoning item so it can be re-injected next turn
|
|
284
|
+
// (carries encrypted_content when include was requested).
|
|
285
|
+
reasoningItems.push(item);
|
|
286
|
+
const summaryParts = item.summary ?? [];
|
|
287
|
+
for (const part of summaryParts) {
|
|
288
|
+
const text = typeof part === "string" ? part : part?.text;
|
|
289
|
+
if (text) {
|
|
290
|
+
reasoningSummary = (reasoningSummary ?? "") + text;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
229
293
|
} else if (item.type === "function_call") {
|
|
230
294
|
toolCalls.push({
|
|
231
295
|
id: item.call_id,
|
|
@@ -245,12 +309,17 @@ export class GenericXAIClient implements GenericClient {
|
|
|
245
309
|
role: "assistant",
|
|
246
310
|
content: textContent,
|
|
247
311
|
...(toolCalls.length > 0 && { tool_calls: toolCalls }),
|
|
312
|
+
...(reasoningSummary && { reasoning_summary: reasoningSummary }),
|
|
313
|
+
...(reasoningItems.length > 0 && {
|
|
314
|
+
_reasoning_details: { provider: "xai", items: reasoningItems },
|
|
315
|
+
}),
|
|
248
316
|
},
|
|
249
317
|
},
|
|
250
318
|
],
|
|
251
319
|
model: options.model,
|
|
252
320
|
usage,
|
|
253
321
|
usd_cost: usdCost,
|
|
322
|
+
response_id: data.id,
|
|
254
323
|
};
|
|
255
324
|
}
|
|
256
325
|
|
package/src/commands/agent.ts
CHANGED
|
@@ -55,6 +55,10 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
|
|
|
55
55
|
)
|
|
56
56
|
.option("--model <model>", "Specific model for the provider")
|
|
57
57
|
.option("--agent-name <name>", "Which agent to use", "Patcher")
|
|
58
|
+
.option(
|
|
59
|
+
"--reasoning-effort <effort>",
|
|
60
|
+
"Reasoning effort: none, low, medium, or high. Use 'none' to disable thinking on models that support it (e.g. for faster computer-use)."
|
|
61
|
+
)
|
|
58
62
|
.option(
|
|
59
63
|
"--max-time-limit <minutes>",
|
|
60
64
|
"Time limit for agent execution (minutes)",
|
|
@@ -116,17 +120,29 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
|
|
|
116
120
|
options.taskId,
|
|
117
121
|
options.messageId
|
|
118
122
|
);
|
|
123
|
+
// Restore the exact model/provider/reasoning the original run used
|
|
124
|
+
// (falls back to agent defaults when not persisted / remote-only).
|
|
125
|
+
const savedSettings = await agentModule.loadTaskSettings(
|
|
126
|
+
options.taskId,
|
|
127
|
+
options.messageId
|
|
128
|
+
);
|
|
119
129
|
const resumeInput =
|
|
120
130
|
options.input || "Please continue from where you left off.";
|
|
121
131
|
|
|
122
132
|
await agentModule.initialize(chatService);
|
|
123
133
|
const { taskCompleted: resumed } =
|
|
124
134
|
await agentModule.resumeFromMessages({
|
|
125
|
-
agentName:
|
|
135
|
+
agentName:
|
|
136
|
+
options.agentName || savedSettings.agentName || "Patcher",
|
|
126
137
|
input: resumeInput,
|
|
127
138
|
threads,
|
|
128
139
|
messageId: options.messageId,
|
|
129
140
|
taskId: options.taskId,
|
|
141
|
+
// CLI-provided model/provider still win over persisted ones.
|
|
142
|
+
model: options.model || savedSettings.model,
|
|
143
|
+
provider: options.provider || savedSettings.provider,
|
|
144
|
+
reasoningEffort: savedSettings.reasoningEffort,
|
|
145
|
+
summarizeReasoning: savedSettings.summarizeReasoning,
|
|
130
146
|
});
|
|
131
147
|
await resumed;
|
|
132
148
|
return;
|
|
@@ -208,6 +224,9 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
|
|
|
208
224
|
input,
|
|
209
225
|
maxTimeLimit: parseInt(options.maxTimeLimit, 10),
|
|
210
226
|
maxSpendLimit: parseFloat(options.maxSpendLimit),
|
|
227
|
+
...(options.reasoningEffort
|
|
228
|
+
? { reasoningEffort: options.reasoningEffort }
|
|
229
|
+
: {}),
|
|
211
230
|
...(behaviorSystemPrompt ? { systemPrompt: behaviorSystemPrompt } : {}),
|
|
212
231
|
...(behaviorModel ? { model: behaviorModel } : {}),
|
|
213
232
|
run: true,
|
package/src/commands/sandbox.ts
CHANGED
|
@@ -60,6 +60,7 @@ interface SandboxSnapshot {
|
|
|
60
60
|
status: string;
|
|
61
61
|
errorMsg?: string | null;
|
|
62
62
|
statusMessage?: string | null;
|
|
63
|
+
regenerationSandboxId?: string | null;
|
|
63
64
|
snapshotContent?: string;
|
|
64
65
|
setupScript?: string | null;
|
|
65
66
|
}
|
|
@@ -122,6 +123,51 @@ async function resolveSnapshot(
|
|
|
122
123
|
|
|
123
124
|
// ─── Polling helpers ──────────────────────────────────────────────────────────
|
|
124
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Poll a snapshot's status until regeneration is complete.
|
|
128
|
+
*
|
|
129
|
+
* The backend's POST /regenerate endpoint is fire-and-forget — it returns
|
|
130
|
+
* immediately with regenerationSandboxId set. We poll GET /snapshots/:id
|
|
131
|
+
* until one of:
|
|
132
|
+
* - regenerationSandboxId is null AND status === "ready" → success
|
|
133
|
+
* - status === "error" → failure
|
|
134
|
+
* - deadline exceeded → timeout
|
|
135
|
+
*
|
|
136
|
+
* statusMessage is printed whenever it changes so the user sees progress.
|
|
137
|
+
*/
|
|
138
|
+
async function pollRegeneration(
|
|
139
|
+
sandboxId: string,
|
|
140
|
+
snapshotId: string,
|
|
141
|
+
timeoutMs: number
|
|
142
|
+
): Promise<SandboxSnapshot> {
|
|
143
|
+
const deadline = Date.now() + timeoutMs;
|
|
144
|
+
let lastStatus = "";
|
|
145
|
+
let lastMessage = "";
|
|
146
|
+
while (Date.now() < deadline) {
|
|
147
|
+
const snap = await apiGet<SandboxSnapshot>(
|
|
148
|
+
`/sandboxes/${sandboxId}/snapshots/${snapshotId}`
|
|
149
|
+
);
|
|
150
|
+
if (snap.status !== lastStatus) {
|
|
151
|
+
console.log(` [snapshot] status: ${snap.status}`);
|
|
152
|
+
lastStatus = snap.status;
|
|
153
|
+
}
|
|
154
|
+
if (snap.statusMessage && snap.statusMessage !== lastMessage) {
|
|
155
|
+
console.log(` [snapshot] ${snap.statusMessage}`);
|
|
156
|
+
lastMessage = snap.statusMessage;
|
|
157
|
+
}
|
|
158
|
+
if (!snap.regenerationSandboxId && snap.status === "ready") return snap;
|
|
159
|
+
if (snap.status === "error") {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Snapshot regeneration failed: ${snap.errorMsg || snap.statusMessage || "(no details)"}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
165
|
+
}
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Timeout waiting for snapshot regeneration to complete (${timeoutMs}ms). Last status: ${lastStatus}`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
125
171
|
async function pollSandboxStatus(
|
|
126
172
|
sandboxId: string,
|
|
127
173
|
targetStatuses: string[],
|
|
@@ -373,7 +419,7 @@ export function addSandboxCommand(program: Command): void {
|
|
|
373
419
|
.option("--sandbox-name <name>", "Source sandbox name (alternative to --sandbox-id)")
|
|
374
420
|
.option("--snapshot-id <id>", "Snapshot ID to regenerate")
|
|
375
421
|
.option("--snapshot-name <name>", "Snapshot label to regenerate")
|
|
376
|
-
.option("--no-wait", "
|
|
422
|
+
.option("--no-wait", "Trigger regeneration and return immediately (do not poll for completion)")
|
|
377
423
|
.option("--timeout-minutes <min>", "Max time for the full regen cycle", "60")
|
|
378
424
|
.action(
|
|
379
425
|
async (opts: {
|
|
@@ -381,20 +427,18 @@ export function addSandboxCommand(program: Command): void {
|
|
|
381
427
|
sandboxName?: string;
|
|
382
428
|
snapshotId?: string;
|
|
383
429
|
snapshotName?: string;
|
|
384
|
-
wait: boolean;
|
|
430
|
+
wait: boolean;
|
|
385
431
|
timeoutMinutes: string;
|
|
386
432
|
}) => {
|
|
387
433
|
const timeoutMs = Number(opts.timeoutMinutes) * 60 * 1000;
|
|
388
|
-
const deadline = Date.now() + timeoutMs;
|
|
389
434
|
|
|
390
|
-
// ── 1. Resolve sandbox
|
|
435
|
+
// ── 1. Resolve sandbox & snapshot ──────────────────────────────────
|
|
391
436
|
if (!opts.sandboxId && !opts.sandboxName) {
|
|
392
437
|
throw new Error("Provide --sandbox-id or --sandbox-name");
|
|
393
438
|
}
|
|
394
439
|
const sandboxId =
|
|
395
440
|
opts.sandboxId || (await resolveSandboxId(opts.sandboxName!));
|
|
396
441
|
|
|
397
|
-
// ── 2. Resolve snapshot ─────────────────────────────────────────────
|
|
398
442
|
if (!opts.snapshotId && !opts.snapshotName) {
|
|
399
443
|
throw new Error("Provide --snapshot-id or --snapshot-name");
|
|
400
444
|
}
|
|
@@ -410,132 +454,113 @@ export function addSandboxCommand(program: Command): void {
|
|
|
410
454
|
);
|
|
411
455
|
console.log(` Source sandbox: ${sandboxId}`);
|
|
412
456
|
|
|
413
|
-
|
|
457
|
+
// ── 2. Validate preconditions ───────────────────────────────────────
|
|
458
|
+
if (snapshot.status !== "ready" && !snapshot.regenerationSandboxId) {
|
|
414
459
|
throw new Error(
|
|
415
460
|
`Snapshot must be in 'ready' status to regenerate (current: ${snapshot.status})`
|
|
416
461
|
);
|
|
417
462
|
}
|
|
418
|
-
if (!snapshot.setupScript) {
|
|
419
|
-
throw new Error(
|
|
420
|
-
"Snapshot has no setupScript. Add one via the UI before regenerating."
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
463
|
|
|
424
|
-
// ── 3.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
464
|
+
// ── 3. Kick off background regeneration via backend API ─────────────
|
|
465
|
+
// The backend POST /regenerate endpoint returns immediately with the
|
|
466
|
+
// updated snapshot (regenerationSandboxId set). It runs the full
|
|
467
|
+
// cycle (fork ephemeral → setupScript → snapshot) in the background.
|
|
468
|
+
console.log("\n📡 Triggering backend regeneration...");
|
|
469
|
+
const triggered = await apiPost<SandboxSnapshot>(
|
|
470
|
+
`/sandboxes/${sandboxId}/snapshots/${snapshotId}/regenerate`
|
|
430
471
|
);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
472
|
+
console.log(` regenerationSandboxId: ${triggered.regenerationSandboxId || "(none)"}`);
|
|
473
|
+
|
|
474
|
+
if (!opts.wait) {
|
|
475
|
+
console.log(
|
|
476
|
+
`\n✅ Regeneration triggered. Monitor with:\n` +
|
|
477
|
+
` knowhow sandbox snapshot-status --sandbox-id ${sandboxId} --snapshot-id ${snapshotId}`
|
|
435
478
|
);
|
|
479
|
+
return;
|
|
436
480
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
481
|
+
|
|
482
|
+
// ── 4. Poll until complete ──────────────────────────────────────────
|
|
483
|
+
console.log("\n⏳ Waiting for regeneration to complete (Ctrl+C to stop polling)...");
|
|
484
|
+
const done = await pollRegeneration(sandboxId, snapshotId, timeoutMs);
|
|
485
|
+
|
|
486
|
+
console.log(
|
|
487
|
+
`\n✅ Snapshot regeneration complete!\n` +
|
|
488
|
+
` Snapshot ID : ${done.id}\n` +
|
|
489
|
+
` Label : ${done.label || "(none)"}\n` +
|
|
490
|
+
` Status : ${done.status}`
|
|
446
491
|
);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// ── 5. Execute the setupScript ──────────────────────────────────────
|
|
450
|
-
console.log("\n🔧 Step 3/4: Executing setupScript...");
|
|
451
|
-
console.log(` Script length: ${snapshot.setupScript.length} chars`);
|
|
452
|
-
let execResult: { stdout?: string; stderr?: string; exitCode?: number };
|
|
453
|
-
try {
|
|
454
|
-
execResult = await apiPost<{
|
|
455
|
-
stdout?: string;
|
|
456
|
-
stderr?: string;
|
|
457
|
-
exitCode?: number;
|
|
458
|
-
}>(`/sandboxes/${ephemeralId}/exec`, {
|
|
459
|
-
command: "/bin/bash",
|
|
460
|
-
args: ["-c", snapshot.setupScript],
|
|
461
|
-
timeoutMs: Math.min(deadline - Date.now(), 25 * 60 * 1000),
|
|
462
|
-
});
|
|
463
|
-
} catch (err: any) {
|
|
464
|
-
console.error(`\n❌ setupScript exec failed: ${err.message}`);
|
|
465
|
-
await safeDestroy(ephemeralId);
|
|
466
|
-
throw err;
|
|
467
|
-
}
|
|
492
|
+
}
|
|
493
|
+
);
|
|
468
494
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
495
|
+
// ── sandbox snapshot-status ──────────────────────────────────────────────────
|
|
496
|
+
sandboxCmd
|
|
497
|
+
.command("snapshot-status")
|
|
498
|
+
.description(
|
|
499
|
+
"Check and optionally wait for a snapshot's regeneration to complete.\n" +
|
|
500
|
+
"Useful after triggering regeneration with --no-wait."
|
|
501
|
+
)
|
|
502
|
+
.option("--sandbox-id <id>", "Sandbox ID")
|
|
503
|
+
.option("--sandbox-name <name>", "Sandbox name")
|
|
504
|
+
.option("--snapshot-id <id>", "Snapshot ID")
|
|
505
|
+
.option("--snapshot-name <name>", "Snapshot label")
|
|
506
|
+
.option("--wait", "Poll until regeneration completes (or times out)")
|
|
507
|
+
.option("--timeout-minutes <min>", "Max poll time in minutes", "60")
|
|
508
|
+
.option("--json", "Output raw JSON")
|
|
509
|
+
.action(
|
|
510
|
+
async (opts: {
|
|
511
|
+
sandboxId?: string;
|
|
512
|
+
sandboxName?: string;
|
|
513
|
+
snapshotId?: string;
|
|
514
|
+
snapshotName?: string;
|
|
515
|
+
wait?: boolean;
|
|
516
|
+
timeoutMinutes: string;
|
|
517
|
+
json?: boolean;
|
|
518
|
+
}) => {
|
|
519
|
+
if (!opts.sandboxId && !opts.sandboxName) {
|
|
520
|
+
throw new Error("Provide --sandbox-id or --sandbox-name");
|
|
476
521
|
}
|
|
477
|
-
|
|
478
|
-
await
|
|
479
|
-
|
|
522
|
+
const sandboxId =
|
|
523
|
+
opts.sandboxId || (await resolveSandboxId(opts.sandboxName!));
|
|
524
|
+
|
|
525
|
+
if (!opts.snapshotId && !opts.snapshotName) {
|
|
526
|
+
throw new Error("Provide --snapshot-id or --snapshot-name");
|
|
480
527
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
snapshotContent: snapshot.snapshotContent || "full",
|
|
494
|
-
snapshotType: "full",
|
|
495
|
-
}
|
|
528
|
+
const snapshot = opts.snapshotId
|
|
529
|
+
? await apiGet<SandboxSnapshot>(
|
|
530
|
+
`/sandboxes/${sandboxId}/snapshots/${opts.snapshotId}`
|
|
531
|
+
)
|
|
532
|
+
: await resolveSnapshot(sandboxId, opts.snapshotName!);
|
|
533
|
+
|
|
534
|
+
if (opts.wait && snapshot.regenerationSandboxId) {
|
|
535
|
+
console.log("⏳ Waiting for regeneration to complete...");
|
|
536
|
+
const done = await pollRegeneration(
|
|
537
|
+
sandboxId,
|
|
538
|
+
snapshot.id,
|
|
539
|
+
Number(opts.timeoutMinutes) * 60 * 1000
|
|
496
540
|
);
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
541
|
+
if (opts.json) {
|
|
542
|
+
console.log(JSON.stringify(done, null, 2));
|
|
543
|
+
} else {
|
|
544
|
+
console.log(
|
|
545
|
+
`✅ Done — status: ${done.status} label: ${done.label || "(none)"}`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
return;
|
|
501
549
|
}
|
|
502
550
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
newSnapshotId,
|
|
512
|
-
["ready"],
|
|
513
|
-
Math.min(deadline - Date.now(), 20 * 60 * 1000)
|
|
551
|
+
if (opts.json) {
|
|
552
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
553
|
+
} else {
|
|
554
|
+
const regenInfo = snapshot.regenerationSandboxId
|
|
555
|
+
? `🔄 regenerating (ephemeral: ${snapshot.regenerationSandboxId})`
|
|
556
|
+
: `✅ ${snapshot.status}`;
|
|
557
|
+
console.log(
|
|
558
|
+
`Snapshot ${snapshot.id} label=${snapshot.label || "(none)"} ${regenInfo}`
|
|
514
559
|
);
|
|
515
|
-
|
|
560
|
+
if (snapshot.statusMessage) {
|
|
561
|
+
console.log(` message: ${snapshot.statusMessage}`);
|
|
562
|
+
}
|
|
516
563
|
}
|
|
517
|
-
|
|
518
|
-
// ── 7. Destroy ephemeral sandbox ────────────────────────────────────
|
|
519
|
-
console.log("\n🗑 Destroying ephemeral sandbox...");
|
|
520
|
-
await safeDestroy(ephemeralId);
|
|
521
|
-
console.log(" ✓ Destroyed");
|
|
522
|
-
|
|
523
|
-
console.log(
|
|
524
|
-
`\n✅ Snapshot regeneration complete!\n` +
|
|
525
|
-
` Old snapshot ID : ${snapshotId}\n` +
|
|
526
|
-
` New snapshot ID : ${newSnapshotId}\n` +
|
|
527
|
-
` Label : ${newLabel}\n\n` +
|
|
528
|
-
`ℹ The old snapshot still exists. Clean it up with:\n` +
|
|
529
|
-
` knowhow sandbox delete-snapshot --sandbox-id ${sandboxId} --snapshot-id ${snapshotId}`
|
|
530
|
-
);
|
|
531
564
|
}
|
|
532
565
|
);
|
|
533
566
|
}
|
|
534
|
-
|
|
535
|
-
async function safeDestroy(sandboxId: string): Promise<void> {
|
|
536
|
-
try {
|
|
537
|
-
await apiDelete(`/sandboxes/${sandboxId}`);
|
|
538
|
-
} catch (err: any) {
|
|
539
|
-
console.warn(`⚠ Could not destroy sandbox ${sandboxId}: ${err.message}`);
|
|
540
|
-
}
|
|
541
|
-
}
|
package/src/index.ts
CHANGED
|
@@ -51,6 +51,14 @@ export {
|
|
|
51
51
|
} from "./generate";
|
|
52
52
|
|
|
53
53
|
export * as clients from "./clients";
|
|
54
|
+
export type {
|
|
55
|
+
Tool,
|
|
56
|
+
ToolProp,
|
|
57
|
+
ToolCall,
|
|
58
|
+
Message,
|
|
59
|
+
CompletionOptions,
|
|
60
|
+
CompletionResponse,
|
|
61
|
+
} from "./clients/types";
|
|
54
62
|
export * as agents from "./agents";
|
|
55
63
|
export * as services from "./services";
|
|
56
64
|
export * as embeddings from "./embeddings";
|
|
@@ -61,6 +69,7 @@ export * as ai from "./ai";
|
|
|
61
69
|
// Export module system types for external modules
|
|
62
70
|
export * from "./services/modules/types";
|
|
63
71
|
export { ModulesService } from "./services/modules";
|
|
72
|
+
export * from "./services/modules/computerUse";
|
|
64
73
|
// Export conversion types for external modules (e.g. knowhow-module-pdf)
|
|
65
74
|
export * from "./services/conversion/types";
|
|
66
75
|
export { ConversionService } from "./services/conversion/ConversionService";
|