toolnexus 0.28.0 → 0.29.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/README.md +55 -15
- package/dist/client.d.ts +3 -5
- package/dist/client.js +17 -7
- package/dist/media.d.ts +465 -54
- package/dist/media.js +1819 -230
- package/dist/wire.d.ts +11 -1
- package/dist/wire.js +20 -3
- package/package.json +1 -1
- package/src/client.ts +12 -7
- package/src/media.ts +2034 -271
- package/src/wire.ts +19 -2
package/dist/wire.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* to the transcript, the `ConversationStore` or `translate` output — which is what keeps a
|
|
18
18
|
* mid-conversation provider switch free of OpenAI-shaped residue.
|
|
19
19
|
*/
|
|
20
|
-
import type { UnsupportedPartMode } from "./content.js";
|
|
20
|
+
import type { ContentPart, UnsupportedPartMode } from "./content.js";
|
|
21
21
|
/** Shared shaping options for a wire build. */
|
|
22
22
|
export interface WireOptions {
|
|
23
23
|
onUnsupportedPart?: UnsupportedPartMode;
|
|
@@ -36,5 +36,15 @@ export declare function toOpenAIWire(messages: any[], opts?: WireOptions): any[]
|
|
|
36
36
|
* carries parts gets them natively inside its own `content`, keyed to its `tool_use_id`.
|
|
37
37
|
*/
|
|
38
38
|
export declare function toAnthropicWire(messages: any[], opts?: WireOptions): any[];
|
|
39
|
+
/**
|
|
40
|
+
* The parts of a tool result that reach the model (SPEC §7G.6). A media tool result
|
|
41
|
+
* (`metadata.source === "media"`) contributes only its **image** parts; its video (`file`) and
|
|
42
|
+
* `audio` parts stay in `toolCalls[].parts` / `RunResult.files` and the model sees just the
|
|
43
|
+
* output line. Every other tool result keeps all its parts.
|
|
44
|
+
*/
|
|
45
|
+
export declare function modelParts(result: {
|
|
46
|
+
parts?: ContentPart[];
|
|
47
|
+
metadata?: Record<string, unknown>;
|
|
48
|
+
}): ContentPart[];
|
|
39
49
|
/** Validate every attached part before assembly, so `maxPartBytes` cannot be bypassed. */
|
|
40
50
|
export declare function checkPromptParts(prompt: unknown, opts: WireOptions): void;
|
package/dist/wire.js
CHANGED
|
@@ -41,7 +41,7 @@ export function toOpenAIWire(messages, opts = {}) {
|
|
|
41
41
|
const parts = partsOf(m);
|
|
42
42
|
const { parts: _drop, ...rest } = m ?? {};
|
|
43
43
|
if (!parts) {
|
|
44
|
-
out.push(m);
|
|
44
|
+
out.push(m && "parts" in m ? rest : m);
|
|
45
45
|
continue;
|
|
46
46
|
}
|
|
47
47
|
const { texts, others } = split(parts);
|
|
@@ -80,8 +80,13 @@ export function toAnthropicWire(messages, opts = {}) {
|
|
|
80
80
|
if (block?.type !== "tool_result")
|
|
81
81
|
return block;
|
|
82
82
|
const parts = partsOf(block);
|
|
83
|
-
if (!parts)
|
|
84
|
-
|
|
83
|
+
if (!parts) {
|
|
84
|
+
if (!("parts" in block))
|
|
85
|
+
return block;
|
|
86
|
+
touched = true;
|
|
87
|
+
const { parts: _drop, ...rest } = block;
|
|
88
|
+
return rest;
|
|
89
|
+
}
|
|
85
90
|
touched = true;
|
|
86
91
|
const { parts: _drop, ...rest } = block;
|
|
87
92
|
return {
|
|
@@ -95,6 +100,18 @@ export function toAnthropicWire(messages, opts = {}) {
|
|
|
95
100
|
return touched ? { ...m, content } : m;
|
|
96
101
|
});
|
|
97
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* The parts of a tool result that reach the model (SPEC §7G.6). A media tool result
|
|
105
|
+
* (`metadata.source === "media"`) contributes only its **image** parts; its video (`file`) and
|
|
106
|
+
* `audio` parts stay in `toolCalls[].parts` / `RunResult.files` and the model sees just the
|
|
107
|
+
* output line. Every other tool result keeps all its parts.
|
|
108
|
+
*/
|
|
109
|
+
export function modelParts(result) {
|
|
110
|
+
const parts = result.parts ?? [];
|
|
111
|
+
if (result.metadata?.source !== "media")
|
|
112
|
+
return parts;
|
|
113
|
+
return parts.filter((p) => p.type === "image");
|
|
114
|
+
}
|
|
98
115
|
/** Validate every attached part before assembly, so `maxPartBytes` cannot be bypassed. */
|
|
99
116
|
export function checkPromptParts(prompt, opts) {
|
|
100
117
|
if (!Array.isArray(prompt))
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* ("openai" | "anthropic"), and it runs the tool-calling agent loop against a
|
|
4
4
|
* Toolkit. See ../../SPEC.md §8.
|
|
5
5
|
*/
|
|
6
|
+
import { mediaPartPath } from "./media.js"
|
|
6
7
|
import type { Toolkit } from "./toolkit.js"
|
|
7
8
|
import type { Tool, ToolHints, ToolResult, Request, Answer } from "./types.js"
|
|
8
9
|
import { pendingOf } from "./types.js"
|
|
@@ -12,7 +13,7 @@ import { buildToolPlan, toolSearch, unwrapCallTool, type ToolPlan, type ToolTier
|
|
|
12
13
|
import { isRetryableStatus, retryAfterMs } from "./retry.js"
|
|
13
14
|
import { isConcurrencySafe, resolveMaxToolConcurrency, scheduleCalls } from "./toolconcurrency.js"
|
|
14
15
|
import type { ContentPart, PromptInput, UnsupportedPartMode } from "./content.js"
|
|
15
|
-
import { toOpenAIWire, toAnthropicWire, checkPromptParts, type WireOptions } from "./wire.js"
|
|
16
|
+
import { toOpenAIWire, toAnthropicWire, checkPromptParts, modelParts, type WireOptions } from "./wire.js"
|
|
16
17
|
import type { TranslateRequest, TranslateResult, TranslatedToolCall } from "./translate.js"
|
|
17
18
|
import {
|
|
18
19
|
finishReasonFor,
|
|
@@ -394,18 +395,20 @@ export interface ToolCallRecord {
|
|
|
394
395
|
}
|
|
395
396
|
|
|
396
397
|
/** §8 RunResult.files entry: a non-text part a tool call produced this run. */
|
|
397
|
-
export type RunFile = { type: "image" | "file" | "audio"; mimeType: string; url?: string; data?: string; name?: string; from: string; callId: string }
|
|
398
|
+
export type RunFile = { type: "image" | "file" | "audio"; mimeType: string; url?: string; data?: string; name?: string; path?: string; from: string; callId: string }
|
|
398
399
|
|
|
399
400
|
/** Model tool-call id of each record, kept off the record itself (§8 shape has no id). */
|
|
400
401
|
const callIds = new WeakMap<ToolCallRecord, string>()
|
|
401
402
|
|
|
402
403
|
/** RunResult.files: every non-text part of every tool call, in call order (§8). */
|
|
403
|
-
function runFiles(toolCalls: ToolCallRecord[]): RunFile[] {
|
|
404
|
+
export function runFiles(toolCalls: ToolCallRecord[]): RunFile[] {
|
|
404
405
|
const out: RunFile[] = []
|
|
405
406
|
for (const r of toolCalls) {
|
|
406
407
|
for (const p of r.parts ?? []) {
|
|
407
408
|
if (p.type === "text") continue
|
|
408
|
-
|
|
409
|
+
// D20: a media file's local path rides on the run file, never on the part.
|
|
410
|
+
const local = mediaPartPath(p)
|
|
411
|
+
out.push({ ...p, ...(local !== undefined ? { path: local } : {}), from: r.name, callId: callIds.get(r) ?? "" })
|
|
409
412
|
}
|
|
410
413
|
}
|
|
411
414
|
return out
|
|
@@ -2182,9 +2185,10 @@ function objArgs(v: unknown): Record<string, unknown> {
|
|
|
2182
2185
|
*/
|
|
2183
2186
|
function toolMessage(id: string | undefined, name: string, result: ToolResult): any {
|
|
2184
2187
|
const m: any = { role: "tool", tool_call_id: id, content: result.output }
|
|
2185
|
-
|
|
2188
|
+
const parts = modelParts(result)
|
|
2189
|
+
if (parts.length) {
|
|
2186
2190
|
m.name = name
|
|
2187
|
-
m.parts =
|
|
2191
|
+
m.parts = parts
|
|
2188
2192
|
}
|
|
2189
2193
|
return m
|
|
2190
2194
|
}
|
|
@@ -2192,7 +2196,8 @@ function toolMessage(id: string | undefined, name: string, result: ToolResult):
|
|
|
2192
2196
|
/** A canonical Anthropic `tool_result` block; parts ride along and are emitted natively. */
|
|
2193
2197
|
function toolResultBlock(id: string | undefined, result: ToolResult): any {
|
|
2194
2198
|
const b: any = { type: "tool_result", tool_use_id: id, content: result.output, is_error: result.isError }
|
|
2195
|
-
|
|
2199
|
+
const parts = modelParts(result)
|
|
2200
|
+
if (parts.length) b.parts = parts
|
|
2196
2201
|
return b
|
|
2197
2202
|
}
|
|
2198
2203
|
|