@deepstrike/sdk 0.2.22 → 0.2.24
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/governance.d.ts +21 -0
- package/dist/governance.js +32 -0
- package/dist/harness/harness.d.ts +30 -0
- package/dist/harness/harness.js +40 -20
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -0
- package/dist/providers/anthropic.js +60 -1
- package/dist/runtime/execution-plane.js +32 -4
- package/dist/runtime/mcp-proxy-plane.js +2 -1
- package/dist/runtime/process-sandbox-plane.js +2 -1
- package/dist/runtime/remote-vpc-plane.js +2 -1
- package/dist/runtime/runner.d.ts +21 -0
- package/dist/runtime/runner.js +425 -333
- package/dist/tools/errors.d.ts +59 -0
- package/dist/tools/errors.js +113 -0
- package/dist/tools/index.d.ts +9 -1
- package/dist/tools/index.js +36 -1
- package/dist/types.d.ts +19 -0
- package/package.json +2 -2
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ToolExecContext, RegisteredTool } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Stable JSON shape returned to the model by `safeTool`. The model can branch on `code` instead
|
|
4
|
+
* of pattern-matching a free-form string. `hint` is the self-correcting affordance — a short
|
|
5
|
+
* suggestion ("call document_outline first") that the agent can follow on its own.
|
|
6
|
+
*/
|
|
7
|
+
export interface ToolEnvelopeOk<T = unknown> {
|
|
8
|
+
success: true;
|
|
9
|
+
data?: T;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolEnvelopeFail {
|
|
12
|
+
success: false;
|
|
13
|
+
code: string;
|
|
14
|
+
error: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
}
|
|
17
|
+
export type ToolEnvelope<T = unknown> = ToolEnvelopeOk<T> | ToolEnvelopeFail;
|
|
18
|
+
/**
|
|
19
|
+
* Error class understood by `safeTool` and by the runtime's error-aware serialization. A throw
|
|
20
|
+
* of `ToolError` produces `{ success:false, code, error, hint? }`; a plain `Error` (or anything
|
|
21
|
+
* with a string `.code`/`.hint`) is honored too, so existing code that already sets `code` on a
|
|
22
|
+
* custom Error keeps working without migration.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ToolError extends Error {
|
|
25
|
+
code: string;
|
|
26
|
+
hint?: string;
|
|
27
|
+
constructor(message: string, opts?: {
|
|
28
|
+
code?: string;
|
|
29
|
+
hint?: string;
|
|
30
|
+
cause?: unknown;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export declare function ok<T>(data?: T): ToolEnvelopeOk<T>;
|
|
34
|
+
export declare function fail(code: string, error: string, hint?: string): ToolEnvelopeFail;
|
|
35
|
+
/**
|
|
36
|
+
* Error-aware serialization for tool-execution error paths. Replaces `String(err)` at the
|
|
37
|
+
* sites that hand the model (or the host's stream) a failure message.
|
|
38
|
+
*
|
|
39
|
+
* - `Error` with no extra fields → `err.message` (clean, no `"Error: "` prefix).
|
|
40
|
+
* - `Error` carrying `code` / `hint` / `cause` → JSON `{message, name?, code?, hint?, cause?}`.
|
|
41
|
+
* - Plain objects → `JSON.stringify(...)` (replaces the old `"[object Object]"`).
|
|
42
|
+
* - Primitives / null / undefined → `String(...)` (unchanged).
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatToolError(err: unknown): string;
|
|
45
|
+
/**
|
|
46
|
+
* `tool()` equivalent that wraps the body in a try/catch and returns a stable
|
|
47
|
+
* `{success, code, error, hint?}` JSON envelope to the model:
|
|
48
|
+
*
|
|
49
|
+
* - body returns plain data → `{success:true, data}`
|
|
50
|
+
* - body returns an envelope (via `ok()`/`fail()`) → passed through
|
|
51
|
+
* - body throws `ToolError` → `{success:false, code, error, hint?}`
|
|
52
|
+
* - body throws any other `Error` → `{success:false, code: error.code ?? "internal", error: error.message}`
|
|
53
|
+
* - body throws a non-Error → `{success:false, code:"internal", error: formatToolError(...)}`
|
|
54
|
+
*
|
|
55
|
+
* The classic `tool()` factory is unchanged. `safeTool` is opt-in: import and switch one tool at
|
|
56
|
+
* a time. Designed for the consumer-side pattern users had to hand-roll to escape the legacy
|
|
57
|
+
* `String(err)` foot-gun.
|
|
58
|
+
*/
|
|
59
|
+
export declare function safeTool<T = unknown>(name: string, description: string, parameters: Record<string, unknown>, fn: (args: Record<string, unknown>, ctx?: ToolExecContext) => Promise<ToolEnvelope<T> | T> | ToolEnvelope<T> | T): RegisteredTool;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { tool } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error class understood by `safeTool` and by the runtime's error-aware serialization. A throw
|
|
4
|
+
* of `ToolError` produces `{ success:false, code, error, hint? }`; a plain `Error` (or anything
|
|
5
|
+
* with a string `.code`/`.hint`) is honored too, so existing code that already sets `code` on a
|
|
6
|
+
* custom Error keeps working without migration.
|
|
7
|
+
*/
|
|
8
|
+
export class ToolError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
hint;
|
|
11
|
+
constructor(message, opts = {}) {
|
|
12
|
+
super(message, opts.cause !== undefined ? { cause: opts.cause } : undefined);
|
|
13
|
+
this.name = "ToolError";
|
|
14
|
+
this.code = opts.code ?? "internal";
|
|
15
|
+
if (opts.hint !== undefined)
|
|
16
|
+
this.hint = opts.hint;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function ok(data) {
|
|
20
|
+
return data === undefined ? { success: true } : { success: true, data };
|
|
21
|
+
}
|
|
22
|
+
export function fail(code, error, hint) {
|
|
23
|
+
return hint === undefined ? { success: false, code, error } : { success: false, code, error, hint };
|
|
24
|
+
}
|
|
25
|
+
function isEnvelope(v) {
|
|
26
|
+
return typeof v === "object" && v !== null && "success" in v &&
|
|
27
|
+
typeof v.success === "boolean";
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Error-aware serialization for tool-execution error paths. Replaces `String(err)` at the
|
|
31
|
+
* sites that hand the model (or the host's stream) a failure message.
|
|
32
|
+
*
|
|
33
|
+
* - `Error` with no extra fields → `err.message` (clean, no `"Error: "` prefix).
|
|
34
|
+
* - `Error` carrying `code` / `hint` / `cause` → JSON `{message, name?, code?, hint?, cause?}`.
|
|
35
|
+
* - Plain objects → `JSON.stringify(...)` (replaces the old `"[object Object]"`).
|
|
36
|
+
* - Primitives / null / undefined → `String(...)` (unchanged).
|
|
37
|
+
*/
|
|
38
|
+
export function formatToolError(err) {
|
|
39
|
+
if (err == null)
|
|
40
|
+
return String(err);
|
|
41
|
+
if (typeof err === "string")
|
|
42
|
+
return err;
|
|
43
|
+
if (err instanceof Error) {
|
|
44
|
+
const anyErr = err;
|
|
45
|
+
const code = anyErr.code;
|
|
46
|
+
const hint = anyErr.hint;
|
|
47
|
+
const cause = anyErr.cause;
|
|
48
|
+
if (code === undefined && hint === undefined && cause === undefined) {
|
|
49
|
+
return err.message || err.name || "Error";
|
|
50
|
+
}
|
|
51
|
+
const payload = { message: err.message };
|
|
52
|
+
if (err.name && err.name !== "Error")
|
|
53
|
+
payload.name = err.name;
|
|
54
|
+
if (code !== undefined)
|
|
55
|
+
payload.code = code;
|
|
56
|
+
if (hint !== undefined)
|
|
57
|
+
payload.hint = hint;
|
|
58
|
+
if (cause !== undefined)
|
|
59
|
+
payload.cause = cause instanceof Error ? cause.message : cause;
|
|
60
|
+
try {
|
|
61
|
+
return JSON.stringify(payload);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return err.message || err.name || "Error";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (typeof err === "object") {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.stringify(err);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return Object.prototype.toString.call(err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return String(err);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `tool()` equivalent that wraps the body in a try/catch and returns a stable
|
|
79
|
+
* `{success, code, error, hint?}` JSON envelope to the model:
|
|
80
|
+
*
|
|
81
|
+
* - body returns plain data → `{success:true, data}`
|
|
82
|
+
* - body returns an envelope (via `ok()`/`fail()`) → passed through
|
|
83
|
+
* - body throws `ToolError` → `{success:false, code, error, hint?}`
|
|
84
|
+
* - body throws any other `Error` → `{success:false, code: error.code ?? "internal", error: error.message}`
|
|
85
|
+
* - body throws a non-Error → `{success:false, code:"internal", error: formatToolError(...)}`
|
|
86
|
+
*
|
|
87
|
+
* The classic `tool()` factory is unchanged. `safeTool` is opt-in: import and switch one tool at
|
|
88
|
+
* a time. Designed for the consumer-side pattern users had to hand-roll to escape the legacy
|
|
89
|
+
* `String(err)` foot-gun.
|
|
90
|
+
*/
|
|
91
|
+
export function safeTool(name, description, parameters, fn) {
|
|
92
|
+
const wrapped = async (args, ctx) => {
|
|
93
|
+
try {
|
|
94
|
+
const result = await fn(args, ctx);
|
|
95
|
+
if (isEnvelope(result))
|
|
96
|
+
return JSON.stringify(result);
|
|
97
|
+
return JSON.stringify(ok(result));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err instanceof ToolError) {
|
|
101
|
+
return JSON.stringify(fail(err.code, err.message || err.name, err.hint));
|
|
102
|
+
}
|
|
103
|
+
if (err instanceof Error) {
|
|
104
|
+
const anyErr = err;
|
|
105
|
+
const code = typeof anyErr.code === "string" ? anyErr.code : "internal";
|
|
106
|
+
const hint = typeof anyErr.hint === "string" ? anyErr.hint : undefined;
|
|
107
|
+
return JSON.stringify(fail(code, err.message || err.name || "Error", hint));
|
|
108
|
+
}
|
|
109
|
+
return JSON.stringify(fail("internal", formatToolError(err)));
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
return tool(name, description, parameters, wrapped);
|
|
113
|
+
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import type { ToolChunk, ToolSchema, ToolResult } from "../types.js";
|
|
2
2
|
/** M3/G4: the runtime context a tool may read when executing. Carries the working directory the tool
|
|
3
3
|
* should operate in — set to a sub-agent's git worktree for `isolation: "worktree"` nodes. A narrow,
|
|
4
|
-
* dependency-free shape; the execution plane's `RunContext` is structurally assignable to it.
|
|
4
|
+
* dependency-free shape; the execution plane's `RunContext` is structurally assignable to it.
|
|
5
|
+
*
|
|
6
|
+
* `audit` is the "best-effort post-commit side-effect" channel: wrap an audit-log write,
|
|
7
|
+
* metrics emit, or any non-essential persistence in `await ctx.audit(label, () => store.write(...))`.
|
|
8
|
+
* If the side-effect throws, the failure is recorded as a `tool_audit_failed` stream event and
|
|
9
|
+
* the tool still completes successfully — avoiding the foot-gun where a transient audit-store
|
|
10
|
+
* outage flips an already-committed write into `isError: true` and triggers a duplicate retry. */
|
|
5
11
|
export interface ToolExecContext {
|
|
6
12
|
cwd?: string;
|
|
13
|
+
audit?: (label: string, fn: () => Promise<void> | void) => Promise<void>;
|
|
7
14
|
}
|
|
8
15
|
export interface RegisteredTool {
|
|
9
16
|
schema: ToolSchema;
|
|
@@ -23,4 +30,5 @@ export declare function executeTools(calls: {
|
|
|
23
30
|
name: string;
|
|
24
31
|
arguments: string;
|
|
25
32
|
}[], registry: Map<string, RegisteredTool>): Promise<ToolResult[]>;
|
|
33
|
+
export declare function maybeWarnFailureShapedChunk(toolName: string, deltaText: string): void;
|
|
26
34
|
export declare const readFile: RegisteredTool;
|
package/dist/tools/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatToolError } from "./errors.js";
|
|
1
2
|
export function tool(name, description, parameters, fn) {
|
|
2
3
|
return {
|
|
3
4
|
schema: { name, description, parameters: JSON.stringify(parameters) },
|
|
@@ -169,10 +170,44 @@ export async function executeTools(calls, registry) {
|
|
|
169
170
|
return { callId: c.id, output, isError: false };
|
|
170
171
|
}
|
|
171
172
|
catch (err) {
|
|
172
|
-
return { callId: c.id, output:
|
|
173
|
+
return { callId: c.id, output: formatToolError(err), isError: true };
|
|
173
174
|
}
|
|
174
175
|
}));
|
|
175
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* One-shot heuristic: detect when a streaming tool yielded text that *looks* like a failure
|
|
179
|
+
* envelope. The runtime cannot block the tool from doing it, but we warn (once per tool) so
|
|
180
|
+
* the author migrates to throwing — the canonical "streaming tool fails" path. Aligns with
|
|
181
|
+
* the non-streaming tool() / safeTool() contract: failures throw, successes return data.
|
|
182
|
+
*/
|
|
183
|
+
const _warnedFailureShapes = new Set();
|
|
184
|
+
export function maybeWarnFailureShapedChunk(toolName, deltaText) {
|
|
185
|
+
if (!deltaText || _warnedFailureShapes.has(toolName))
|
|
186
|
+
return;
|
|
187
|
+
const trimmed = deltaText.trim();
|
|
188
|
+
if (trimmed.length < 2 || trimmed[0] !== "{")
|
|
189
|
+
return;
|
|
190
|
+
let parsed;
|
|
191
|
+
try {
|
|
192
|
+
parsed = JSON.parse(trimmed);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
198
|
+
return;
|
|
199
|
+
const obj = parsed;
|
|
200
|
+
const looksLikeFailure = obj.success === false ||
|
|
201
|
+
obj.isError === true ||
|
|
202
|
+
obj.is_error === true;
|
|
203
|
+
if (!looksLikeFailure)
|
|
204
|
+
return;
|
|
205
|
+
_warnedFailureShapes.add(toolName);
|
|
206
|
+
console.warn(`[deepstrike] streaming tool "${toolName}" yielded a failure-shaped chunk ` +
|
|
207
|
+
`(success:false / isError:true). Streaming tools should fail by throwing; ` +
|
|
208
|
+
`the runtime will catch and surface the error consistently. ` +
|
|
209
|
+
`Returning a failure-shaped chunk is a foot-gun: the kernel still sees isError:false.`);
|
|
210
|
+
}
|
|
176
211
|
export const readFile = tool("read_file", "Read the contents of a file.", { type: "object", properties: { path: { type: "string" } }, required: ["path"] }, async ({ path }) => {
|
|
177
212
|
const { readFile: fsRead } = await import("fs/promises");
|
|
178
213
|
return fsRead(String(path), "utf8");
|
package/dist/types.d.ts
CHANGED
|
@@ -84,6 +84,15 @@ export interface UsageEvent extends StreamEvent {
|
|
|
84
84
|
cacheReadInputTokens?: number;
|
|
85
85
|
/** Prompt tokens written to cache this request (billed ~1.25x). Subset of inputTokens. */
|
|
86
86
|
cacheCreationInputTokens?: number;
|
|
87
|
+
/** I1: per-slot pro-rata attribution of `cacheReadInputTokens`. Estimated, not authoritative —
|
|
88
|
+
* Anthropic returns a single cache-read total, so the SDK divides it evenly across the slots
|
|
89
|
+
* that carried a `cache_control` breakpoint on the request. Missing when the provider doesn't
|
|
90
|
+
* honor `cache_control` (OpenAI-family auto-cache) or when no breakpoints were placed. */
|
|
91
|
+
cacheReadInputTokensBySlot?: {
|
|
92
|
+
system?: number;
|
|
93
|
+
tools?: number;
|
|
94
|
+
messages?: number;
|
|
95
|
+
};
|
|
87
96
|
}
|
|
88
97
|
export type ToolChunk = string | {
|
|
89
98
|
type: "text";
|
|
@@ -180,6 +189,16 @@ export interface ToolDeniedEvent extends StreamEvent {
|
|
|
180
189
|
toolName: string;
|
|
181
190
|
reason: string;
|
|
182
191
|
}
|
|
192
|
+
/** A tool's `ctx.audit(label, fn)` best-effort side-effect threw. The tool itself completed
|
|
193
|
+
* successfully (no isError flip, no retry); this event lets the host log / monitor that an
|
|
194
|
+
* audit-store / metrics-emit / non-essential persistence step failed. */
|
|
195
|
+
export interface ToolAuditFailedEvent extends StreamEvent {
|
|
196
|
+
type: "tool_audit_failed";
|
|
197
|
+
callId: string;
|
|
198
|
+
name: string;
|
|
199
|
+
label: string;
|
|
200
|
+
error: string;
|
|
201
|
+
}
|
|
183
202
|
export interface TokenUsage {
|
|
184
203
|
/** Full prompt size: uncached input + cache reads + cache writes. */
|
|
185
204
|
inputTokens: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@anthropic-ai/sdk": "^0.99.0",
|
|
23
|
-
"@deepstrike/core": "0.2.
|
|
23
|
+
"@deepstrike/core": "0.2.24",
|
|
24
24
|
"@google/generative-ai": "^0.24.1",
|
|
25
25
|
"openai": "^5.23.2"
|
|
26
26
|
},
|