@introspection-sdk/introspection-node 0.4.1 → 0.4.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.
- package/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-check-types.log +4 -0
- package/.turbo/turbo-lint.log +5 -0
- package/dist/ai-sdk-integration.d.ts.map +1 -1
- package/dist/ai-sdk-integration.js +2 -2
- package/dist/claude-hooks.d.ts.map +1 -1
- package/dist/claude-hooks.js +3 -3
- package/dist/client.d.ts +42 -132
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +66 -365
- package/dist/client.js.map +1 -0
- package/dist/converters/claude.d.ts +5 -1
- package/dist/converters/claude.d.ts.map +1 -1
- package/dist/converters/claude.js +7 -0
- package/dist/converters/claude.js.map +1 -0
- package/dist/converters/gemini.d.ts +90 -0
- package/dist/converters/gemini.d.ts.map +1 -0
- package/dist/converters/gemini.js +171 -0
- package/dist/converters/gemini.js.map +1 -0
- package/dist/converters/index.d.ts +1 -1
- package/dist/converters/index.d.ts.map +1 -1
- package/dist/converters/index.js +2 -1
- package/dist/converters/index.js.map +1 -0
- package/dist/converters/openai.js +1 -0
- package/dist/converters/openai.js.map +1 -0
- package/dist/converters/openclaw.d.ts +16 -0
- package/dist/converters/openclaw.d.ts.map +1 -0
- package/dist/converters/openclaw.js +49 -0
- package/dist/converters/openinference.js +1 -0
- package/dist/converters/openinference.js.map +1 -0
- package/dist/converters/vercel.d.ts.map +1 -1
- package/dist/converters/vercel.js +31 -3
- package/dist/converters/vercel.js.map +1 -0
- package/dist/http.d.ts +36 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +145 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +24 -27
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -32
- package/dist/index.js.map +1 -0
- package/dist/langchain-handler.d.ts.map +1 -1
- package/dist/langchain-handler.js +2 -2
- package/dist/mastra-exporter.d.ts.map +1 -1
- package/dist/mastra-exporter.js +2 -2
- package/dist/otel/anthropic.d.ts +62 -0
- package/dist/otel/anthropic.d.ts.map +1 -0
- package/dist/otel/anthropic.js +425 -0
- package/dist/otel/anthropic.js.map +1 -0
- package/dist/otel/claude-hooks.d.ts +346 -0
- package/dist/otel/claude-hooks.d.ts.map +1 -0
- package/dist/otel/claude-hooks.js +645 -0
- package/dist/otel/claude-hooks.js.map +1 -0
- package/dist/otel/claude-wrapper.d.ts +93 -0
- package/dist/otel/claude-wrapper.d.ts.map +1 -0
- package/dist/otel/claude-wrapper.js +155 -0
- package/dist/otel/claude-wrapper.js.map +1 -0
- package/dist/otel/gemini.d.ts +48 -0
- package/dist/otel/gemini.d.ts.map +1 -0
- package/dist/otel/gemini.js +297 -0
- package/dist/otel/gemini.js.map +1 -0
- package/dist/otel/index.d.ts +27 -0
- package/dist/otel/index.d.ts.map +1 -0
- package/dist/otel/index.js +33 -0
- package/dist/otel/index.js.map +1 -0
- package/dist/otel/langchain-handler.d.ts +112 -0
- package/dist/otel/langchain-handler.d.ts.map +1 -0
- package/dist/otel/langchain-handler.js +555 -0
- package/dist/otel/langchain-handler.js.map +1 -0
- package/dist/otel/logs.d.ts +82 -0
- package/dist/otel/logs.d.ts.map +1 -0
- package/dist/otel/logs.js +271 -0
- package/dist/otel/logs.js.map +1 -0
- package/dist/otel/mastra-exporter.d.ts +87 -0
- package/dist/otel/mastra-exporter.d.ts.map +1 -0
- package/dist/otel/mastra-exporter.js +525 -0
- package/dist/otel/mastra-exporter.js.map +1 -0
- package/dist/otel/pi.d.ts +59 -0
- package/dist/otel/pi.d.ts.map +1 -0
- package/dist/otel/pi.js +67 -0
- package/dist/otel/pi.js.map +1 -0
- package/dist/otel/setup.d.ts +23 -0
- package/dist/otel/setup.d.ts.map +1 -0
- package/dist/otel/setup.js +127 -0
- package/dist/otel/setup.js.map +1 -0
- package/dist/otel/span-processor.d.ts +62 -0
- package/dist/otel/span-processor.d.ts.map +1 -0
- package/dist/otel/span-processor.js +319 -0
- package/dist/otel/span-processor.js.map +1 -0
- package/dist/otel/tracing-processor.d.ts +139 -0
- package/dist/otel/tracing-processor.d.ts.map +1 -0
- package/dist/otel/tracing-processor.js +418 -0
- package/dist/otel/tracing-processor.js.map +1 -0
- package/dist/resources/experiments.d.ts +31 -0
- package/dist/resources/experiments.d.ts.map +1 -0
- package/dist/resources/experiments.js +133 -0
- package/dist/resources/experiments.js.map +1 -0
- package/dist/resources/recipes.d.ts +22 -0
- package/dist/resources/recipes.d.ts.map +1 -0
- package/dist/resources/recipes.js +61 -0
- package/dist/resources/recipes.js.map +1 -0
- package/dist/resources/runtimes.d.ts +76 -0
- package/dist/resources/runtimes.d.ts.map +1 -0
- package/dist/resources/runtimes.js +179 -0
- package/dist/resources/runtimes.js.map +1 -0
- package/dist/runner-resources/files.d.ts +35 -0
- package/dist/runner-resources/files.d.ts.map +1 -0
- package/dist/runner-resources/files.js +131 -0
- package/dist/runner-resources/files.js.map +1 -0
- package/dist/runner-resources/tasks.d.ts +39 -0
- package/dist/runner-resources/tasks.d.ts.map +1 -0
- package/dist/runner-resources/tasks.js +129 -0
- package/dist/runner-resources/tasks.js.map +1 -0
- package/dist/runner.d.ts +80 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +159 -0
- package/dist/runner.js.map +1 -0
- package/dist/span-processor.d.ts.map +1 -1
- package/dist/span-processor.js +3 -3
- package/dist/streaming.d.ts +8 -0
- package/dist/streaming.d.ts.map +1 -0
- package/dist/streaming.js +67 -0
- package/dist/streaming.js.map +1 -0
- package/dist/tracing-processor.d.ts.map +1 -1
- package/dist/tracing-processor.js +3 -3
- package/dist/types/genai.d.ts +165 -0
- package/dist/types/genai.d.ts.map +1 -0
- package/dist/types/genai.js +82 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +14 -0
- package/dist/utils.js.map +1 -0
- package/dist/version.js +1 -0
- package/dist/version.js.map +1 -0
- package/package.json +83 -25
- package/src/client.ts +76 -471
- package/src/converters/claude.ts +11 -1
- package/src/converters/gemini.ts +243 -0
- package/src/converters/index.ts +13 -7
- package/src/converters/vercel.ts +41 -3
- package/src/http.ts +163 -0
- package/src/index.ts +110 -75
- package/src/{anthropic.ts → otel/anthropic.ts} +95 -8
- package/src/{claude-hooks.ts → otel/claude-hooks.ts} +125 -24
- package/src/{claude-wrapper.ts → otel/claude-wrapper.ts} +1 -1
- package/src/otel/gemini.ts +397 -0
- package/src/otel/index.ts +80 -0
- package/src/{langchain-handler.ts → otel/langchain-handler.ts} +56 -7
- package/src/otel/logs.ts +394 -0
- package/src/{mastra-exporter.ts → otel/mastra-exporter.ts} +86 -23
- package/src/otel/pi.ts +88 -0
- package/src/otel/setup.ts +174 -0
- package/src/{span-processor.ts → otel/span-processor.ts} +17 -10
- package/src/{tracing-processor.ts → otel/tracing-processor.ts} +100 -23
- package/src/resources/experiments.ts +170 -0
- package/src/resources/recipes.ts +73 -0
- package/src/resources/runtimes.ts +231 -0
- package/src/runner-resources/files.ts +163 -0
- package/src/runner-resources/tasks.ts +156 -0
- package/src/runner.ts +194 -0
- package/src/streaming.ts +57 -0
- package/src/utils.ts +15 -0
- package/src/ai-sdk-integration.ts +0 -689
- package/src/converters/ai-sdk.ts +0 -323
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Gemini (`@google/genai`) format conversion functions for OTel Gen AI
|
|
3
|
+
* Semantic Conventions.
|
|
4
|
+
*
|
|
5
|
+
* Gemini 3.x / Gemini 3.5+ models emit per-part `thoughtSignature` payloads
|
|
6
|
+
* that carry encrypted reasoning context which MUST be passed back on
|
|
7
|
+
* subsequent turns (especially around tool calls) for the model to maintain
|
|
8
|
+
* its chain of thought.
|
|
9
|
+
*
|
|
10
|
+
* @see https://ai.google.dev/gemini-api/docs/thought-signatures
|
|
11
|
+
*
|
|
12
|
+
* Whenever a Gemini part carries a `thoughtSignature`, this converter emits a
|
|
13
|
+
* `thinking` gen_ai part preceding the visible content. If the part is itself a
|
|
14
|
+
* thought summary (`thought: true`), the visible thought text is used as the
|
|
15
|
+
* thinking content; otherwise the signed-but-redacted sentinel
|
|
16
|
+
* `REDACTED_THINKING_CONTENT` (`"[redacted]"`) is used — mirroring the way the
|
|
17
|
+
* Anthropic instrumentor handles `redacted_thinking` blocks.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
InputMessage,
|
|
22
|
+
OutputMessage,
|
|
23
|
+
MessagePart,
|
|
24
|
+
ReasoningPart,
|
|
25
|
+
SystemInstruction,
|
|
26
|
+
ToolDefinition,
|
|
27
|
+
} from "@introspection-sdk/types";
|
|
28
|
+
|
|
29
|
+
/** Sentinel for parts that carry a thought signature but no visible thought content. */
|
|
30
|
+
const REDACTED_THINKING_CONTENT = "[redacted]";
|
|
31
|
+
|
|
32
|
+
/** Provider name reported on Gemini-produced thinking parts. */
|
|
33
|
+
export const GEMINI_PROVIDER_NAME = "gemini";
|
|
34
|
+
|
|
35
|
+
/** A single content part as returned by the `@google/genai` SDK. */
|
|
36
|
+
export interface GeminiPart {
|
|
37
|
+
text?: string;
|
|
38
|
+
thought?: boolean;
|
|
39
|
+
thoughtSignature?: string;
|
|
40
|
+
functionCall?: {
|
|
41
|
+
id?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
args?: unknown;
|
|
44
|
+
};
|
|
45
|
+
functionResponse?: {
|
|
46
|
+
id?: string;
|
|
47
|
+
name?: string;
|
|
48
|
+
response?: unknown;
|
|
49
|
+
};
|
|
50
|
+
[key: string]: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A Content envelope as used in Gemini `contents` requests and candidate outputs. */
|
|
54
|
+
export interface GeminiContent {
|
|
55
|
+
role?: string;
|
|
56
|
+
parts?: GeminiPart[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A single candidate from a `generateContent` response. */
|
|
60
|
+
export interface GeminiCandidate {
|
|
61
|
+
content?: GeminiContent;
|
|
62
|
+
finishReason?: string;
|
|
63
|
+
index?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A Gemini tool function declaration. */
|
|
67
|
+
export interface GeminiFunctionDeclaration {
|
|
68
|
+
name?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
parameters?: unknown;
|
|
71
|
+
parametersJsonSchema?: unknown;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A Gemini tool entry — `{ functionDeclarations: [...] }`. */
|
|
75
|
+
export interface GeminiTool {
|
|
76
|
+
functionDeclarations?: GeminiFunctionDeclaration[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Normalize roles from Gemini to gen_ai. Gemini uses `model` for assistant turns. */
|
|
80
|
+
function normalizeRole(role: string | undefined): InputMessage["role"] {
|
|
81
|
+
if (role === "model") return "assistant";
|
|
82
|
+
if (role === "user" || role === "system" || role === "tool") return role;
|
|
83
|
+
return "user";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert a single Gemini part into zero-or-more gen_ai message parts.
|
|
88
|
+
*
|
|
89
|
+
* A part carrying a `thoughtSignature` always produces a leading `thinking`
|
|
90
|
+
* part so the signed reasoning payload is preserved alongside whatever visible
|
|
91
|
+
* content the part contains.
|
|
92
|
+
*/
|
|
93
|
+
function partToGenAiParts(part: GeminiPart): MessagePart[] {
|
|
94
|
+
const out: MessagePart[] = [];
|
|
95
|
+
const signature = part.thoughtSignature || undefined;
|
|
96
|
+
const isThought = part.thought === true;
|
|
97
|
+
|
|
98
|
+
if (isThought) {
|
|
99
|
+
// Visible thought summary (Gemini emits these when `includeThoughts: true`).
|
|
100
|
+
const reasoning: ReasoningPart = {
|
|
101
|
+
type: "thinking",
|
|
102
|
+
content: part.text || REDACTED_THINKING_CONTENT,
|
|
103
|
+
signature,
|
|
104
|
+
provider_name: GEMINI_PROVIDER_NAME,
|
|
105
|
+
};
|
|
106
|
+
out.push(reasoning);
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (signature) {
|
|
111
|
+
// Signed but non-thought part — emit a redacted thinking prefix carrying the signature.
|
|
112
|
+
const reasoning: ReasoningPart = {
|
|
113
|
+
type: "thinking",
|
|
114
|
+
content: REDACTED_THINKING_CONTENT,
|
|
115
|
+
signature,
|
|
116
|
+
provider_name: GEMINI_PROVIDER_NAME,
|
|
117
|
+
};
|
|
118
|
+
out.push(reasoning);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof part.text === "string" && part.text.length > 0) {
|
|
122
|
+
out.push({ type: "text", content: part.text });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (part.functionCall) {
|
|
126
|
+
out.push({
|
|
127
|
+
type: "tool_call",
|
|
128
|
+
id: part.functionCall.id,
|
|
129
|
+
name: part.functionCall.name || "",
|
|
130
|
+
arguments: part.functionCall.args,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (part.functionResponse) {
|
|
135
|
+
out.push({
|
|
136
|
+
type: "tool_call_response",
|
|
137
|
+
id: part.functionResponse.id,
|
|
138
|
+
name: part.functionResponse.name,
|
|
139
|
+
response: part.functionResponse.response,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Convert a `contents` array from a Gemini request to gen_ai input messages.
|
|
148
|
+
*
|
|
149
|
+
* Accepts a plain string (the convenience form `generateContent({ contents: "hi" })`)
|
|
150
|
+
* or an array of {@link GeminiContent} objects. Roles are normalized: Gemini's
|
|
151
|
+
* `model` becomes `assistant`.
|
|
152
|
+
*
|
|
153
|
+
* @param contents - The Gemini request's `contents` field.
|
|
154
|
+
* @returns Array of {@link InputMessage} objects.
|
|
155
|
+
*/
|
|
156
|
+
export function convertGeminiContentsToInputMessages(
|
|
157
|
+
contents: string | GeminiContent[] | GeminiContent | undefined,
|
|
158
|
+
): InputMessage[] {
|
|
159
|
+
if (!contents) return [];
|
|
160
|
+
|
|
161
|
+
if (typeof contents === "string") {
|
|
162
|
+
return [{ role: "user", parts: [{ type: "text", content: contents }] }];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const list: GeminiContent[] = Array.isArray(contents) ? contents : [contents];
|
|
166
|
+
|
|
167
|
+
const result: InputMessage[] = [];
|
|
168
|
+
for (const c of list) {
|
|
169
|
+
const parts = (c.parts || []).flatMap(partToGenAiParts);
|
|
170
|
+
if (parts.length === 0) continue;
|
|
171
|
+
result.push({ role: normalizeRole(c.role), parts });
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Convert Gemini response candidates to gen_ai output messages.
|
|
178
|
+
*
|
|
179
|
+
* @param candidates - The `candidates` array from a `generateContent` response.
|
|
180
|
+
* @returns Array of {@link OutputMessage} objects (one per candidate that has content).
|
|
181
|
+
*/
|
|
182
|
+
export function convertGeminiCandidatesToOutputMessages(
|
|
183
|
+
candidates: GeminiCandidate[] | undefined,
|
|
184
|
+
): OutputMessage[] {
|
|
185
|
+
if (!candidates || candidates.length === 0) return [];
|
|
186
|
+
|
|
187
|
+
const result: OutputMessage[] = [];
|
|
188
|
+
for (const cand of candidates) {
|
|
189
|
+
const role = normalizeRole(cand.content?.role || "model");
|
|
190
|
+
const parts = (cand.content?.parts || []).flatMap(partToGenAiParts);
|
|
191
|
+
if (parts.length === 0 && !cand.finishReason) continue;
|
|
192
|
+
const msg: OutputMessage = { role, parts };
|
|
193
|
+
if (cand.finishReason) msg.finish_reason = cand.finishReason;
|
|
194
|
+
result.push(msg);
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Convert a Gemini `systemInstruction` field to gen_ai SystemInstruction[].
|
|
201
|
+
*
|
|
202
|
+
* Gemini accepts either a plain string or a {@link GeminiContent} object.
|
|
203
|
+
*/
|
|
204
|
+
export function convertGeminiSystemInstructionToSemconv(
|
|
205
|
+
systemInstruction: string | GeminiContent | undefined,
|
|
206
|
+
): SystemInstruction[] | undefined {
|
|
207
|
+
if (!systemInstruction) return undefined;
|
|
208
|
+
|
|
209
|
+
if (typeof systemInstruction === "string") {
|
|
210
|
+
return [{ type: "text", content: systemInstruction }];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const result = (systemInstruction.parts || []).flatMap(
|
|
214
|
+
(p): SystemInstruction[] =>
|
|
215
|
+
typeof p.text === "string" && p.text.length > 0
|
|
216
|
+
? [{ type: "text", content: p.text }]
|
|
217
|
+
: [],
|
|
218
|
+
);
|
|
219
|
+
return result.length > 0 ? result : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Convert Gemini `tools` (array of `{functionDeclarations}` entries) to gen_ai
|
|
224
|
+
* ToolDefinition[].
|
|
225
|
+
*/
|
|
226
|
+
export function convertGeminiToolsToToolDefinitions(
|
|
227
|
+
tools: GeminiTool[] | undefined,
|
|
228
|
+
): ToolDefinition[] | undefined {
|
|
229
|
+
if (!tools || tools.length === 0) return undefined;
|
|
230
|
+
|
|
231
|
+
const defs: ToolDefinition[] = [];
|
|
232
|
+
for (const tool of tools) {
|
|
233
|
+
for (const fn of tool.functionDeclarations || []) {
|
|
234
|
+
if (!fn.name) continue;
|
|
235
|
+
const def: ToolDefinition = { name: fn.name };
|
|
236
|
+
if (fn.description) def.description = fn.description;
|
|
237
|
+
const params = fn.parametersJsonSchema ?? fn.parameters;
|
|
238
|
+
if (params) def.parameters = params;
|
|
239
|
+
defs.push(def);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return defs.length > 0 ? defs : undefined;
|
|
243
|
+
}
|
package/src/converters/index.ts
CHANGED
|
@@ -24,13 +24,6 @@ export {
|
|
|
24
24
|
|
|
25
25
|
export { isVercelAISpan, convertVercelAIToGenAI } from "./vercel.js";
|
|
26
26
|
|
|
27
|
-
export {
|
|
28
|
-
convertMessagesToInputMessages as convertAISDKMessagesToInputMessages,
|
|
29
|
-
extractSystemInstructions as extractAISDKSystemInstructions,
|
|
30
|
-
buildOutputMessages as buildAISDKOutputMessages,
|
|
31
|
-
convertToolsToToolDefinitions as convertAISDKToolsToToolDefinitions,
|
|
32
|
-
} from "./ai-sdk.js";
|
|
33
|
-
|
|
34
27
|
export {
|
|
35
28
|
convertClaudePromptToInputMessages,
|
|
36
29
|
convertClaudeResponseToOutputMessages,
|
|
@@ -42,3 +35,16 @@ export {
|
|
|
42
35
|
type ClaudeResponse,
|
|
43
36
|
type ClaudeSessionData,
|
|
44
37
|
} from "./claude.js";
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
convertGeminiContentsToInputMessages,
|
|
41
|
+
convertGeminiCandidatesToOutputMessages,
|
|
42
|
+
convertGeminiSystemInstructionToSemconv,
|
|
43
|
+
convertGeminiToolsToToolDefinitions,
|
|
44
|
+
GEMINI_PROVIDER_NAME,
|
|
45
|
+
type GeminiCandidate,
|
|
46
|
+
type GeminiContent,
|
|
47
|
+
type GeminiFunctionDeclaration,
|
|
48
|
+
type GeminiPart,
|
|
49
|
+
type GeminiTool,
|
|
50
|
+
} from "./gemini.js";
|
package/src/converters/vercel.ts
CHANGED
|
@@ -22,7 +22,13 @@ interface VercelContentPart {
|
|
|
22
22
|
name?: string;
|
|
23
23
|
toolCallId?: string;
|
|
24
24
|
id?: string;
|
|
25
|
+
// AI SDK v6 uses `input` for tool-call args; older versions used `args`.
|
|
25
26
|
args?: string | Record<string, unknown>;
|
|
27
|
+
input?: string | Record<string, unknown>;
|
|
28
|
+
// tool-result payload. AI SDK v6 emits `{ output: { type: "text", value } }`
|
|
29
|
+
// for text results; older shapes used `result`.
|
|
30
|
+
output?: { type?: string; value?: unknown } | unknown;
|
|
31
|
+
result?: unknown;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
interface VercelTool {
|
|
@@ -39,6 +45,10 @@ interface GenAIPart {
|
|
|
39
45
|
name?: string;
|
|
40
46
|
id?: string;
|
|
41
47
|
arguments?: string;
|
|
48
|
+
// tool_call_response payload — kept separate from `content` so downstream
|
|
49
|
+
// consumers (and brightstaff signal detectors) can distinguish tool
|
|
50
|
+
// results from plain text.
|
|
51
|
+
response?: string;
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
interface GenAIMessage {
|
|
@@ -247,14 +257,42 @@ function extractParts(content: VercelMessage["content"]): GenAIPart[] {
|
|
|
247
257
|
return { type: "text", content: part.text || part.content || "" };
|
|
248
258
|
}
|
|
249
259
|
if (part.type === "tool-call" || part.type === "tool_call") {
|
|
260
|
+
// AI SDK v6 emits `input`; older versions emitted `args`.
|
|
261
|
+
const rawArgs = part.input ?? part.args;
|
|
250
262
|
return {
|
|
251
263
|
type: "tool_call",
|
|
252
264
|
name: part.toolName || part.name || "",
|
|
253
265
|
id: part.toolCallId || part.id || "",
|
|
254
266
|
arguments:
|
|
255
|
-
typeof
|
|
256
|
-
|
|
257
|
-
|
|
267
|
+
typeof rawArgs === "string" ? rawArgs : JSON.stringify(rawArgs),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (part.type === "tool-result" || part.type === "tool_result") {
|
|
271
|
+
// AI SDK v6 wraps text outputs in `{ output: { type: "text", value } }`.
|
|
272
|
+
// Older shapes used `result`. Unwrap to the raw string when we can so
|
|
273
|
+
// brightstaff's execution.failure.* / environment.exhaustion.* regex
|
|
274
|
+
// detectors see the error text directly — otherwise downstream
|
|
275
|
+
// consumers get a JSON-stringified blob and the canonical
|
|
276
|
+
// `tool_call_response` shape is lost.
|
|
277
|
+
const rawOutput =
|
|
278
|
+
part.result !== undefined ? part.result : (part.output as unknown);
|
|
279
|
+
let response: string;
|
|
280
|
+
if (typeof rawOutput === "string") {
|
|
281
|
+
response = rawOutput;
|
|
282
|
+
} else if (
|
|
283
|
+
rawOutput &&
|
|
284
|
+
typeof rawOutput === "object" &&
|
|
285
|
+
"value" in (rawOutput as Record<string, unknown>)
|
|
286
|
+
) {
|
|
287
|
+
const value = (rawOutput as Record<string, unknown>).value;
|
|
288
|
+
response = typeof value === "string" ? value : JSON.stringify(value);
|
|
289
|
+
} else {
|
|
290
|
+
response = JSON.stringify(rawOutput);
|
|
291
|
+
}
|
|
292
|
+
return {
|
|
293
|
+
type: "tool_call_response",
|
|
294
|
+
id: part.toolCallId || part.id || "",
|
|
295
|
+
response,
|
|
258
296
|
};
|
|
259
297
|
}
|
|
260
298
|
return { type: "text", content: JSON.stringify(part) };
|
package/src/http.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntrospectionAPIError,
|
|
3
|
+
NetworkError,
|
|
4
|
+
apiErrorFromResponse,
|
|
5
|
+
} from "@introspection-sdk/types";
|
|
6
|
+
|
|
7
|
+
export interface ResolvedApiConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Base URL the client will prepend to every request path. For the
|
|
10
|
+
* IntrospectionClient this is the CP API host; for a Runner it is the
|
|
11
|
+
* `deployment.endpoint` returned by CP.
|
|
12
|
+
*/
|
|
13
|
+
apiUrl: string;
|
|
14
|
+
/** Bearer token. Customer API key for CP, runner JWT for DP. */
|
|
15
|
+
token: string;
|
|
16
|
+
additionalHeaders?: Record<string, string>;
|
|
17
|
+
fetch?: typeof fetch;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function joinUrl(base: string, path: string): string {
|
|
21
|
+
const b = base.replace(/\/+$/, "");
|
|
22
|
+
const p = path.startsWith("/") ? path : `/${path}`;
|
|
23
|
+
return `${b}${p}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildQuery(params?: Record<string, unknown>): string {
|
|
27
|
+
if (!params) return "";
|
|
28
|
+
const sp = new URLSearchParams();
|
|
29
|
+
for (const [k, v] of Object.entries(params)) {
|
|
30
|
+
if (v === undefined || v === null) continue;
|
|
31
|
+
if (Array.isArray(v)) {
|
|
32
|
+
for (const item of v) sp.append(k, String(item));
|
|
33
|
+
} else {
|
|
34
|
+
sp.set(k, String(v));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const q = sp.toString();
|
|
38
|
+
return q ? `?${q}` : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Thin HTTP wrapper used by both the CP-bound IntrospectionClient and
|
|
43
|
+
* each DP-bound Runner. Has no opinion on which one it is — the caller
|
|
44
|
+
* picks the base URL and the token.
|
|
45
|
+
*/
|
|
46
|
+
export class HttpClient {
|
|
47
|
+
private readonly fetchImpl: typeof fetch;
|
|
48
|
+
|
|
49
|
+
constructor(private readonly cfg: ResolvedApiConfig) {
|
|
50
|
+
this.fetchImpl = cfg.fetch ?? globalThis.fetch;
|
|
51
|
+
if (!this.fetchImpl) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
"global fetch is unavailable; pass `advanced.fetch` or run on Node 18+",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private headers(extra?: Record<string, string>): Record<string, string> {
|
|
59
|
+
return {
|
|
60
|
+
Authorization: `Bearer ${this.cfg.token}`,
|
|
61
|
+
...(this.cfg.additionalHeaders ?? {}),
|
|
62
|
+
...(extra ?? {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async request<T>(opts: {
|
|
67
|
+
method: string;
|
|
68
|
+
path: string;
|
|
69
|
+
query?: Record<string, unknown>;
|
|
70
|
+
body?: unknown;
|
|
71
|
+
headers?: Record<string, string>;
|
|
72
|
+
expect?: "json" | "empty" | "bytes" | "stream";
|
|
73
|
+
}): Promise<T> {
|
|
74
|
+
const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
|
|
75
|
+
let body: BodyInit | undefined;
|
|
76
|
+
const headers = this.headers(opts.headers);
|
|
77
|
+
if (opts.body instanceof FormData) {
|
|
78
|
+
body = opts.body;
|
|
79
|
+
// let fetch set the multipart boundary
|
|
80
|
+
} else if (opts.body !== undefined) {
|
|
81
|
+
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
82
|
+
body = JSON.stringify(opts.body);
|
|
83
|
+
}
|
|
84
|
+
let res: Response;
|
|
85
|
+
try {
|
|
86
|
+
res = await this.fetchImpl(url, {
|
|
87
|
+
method: opts.method,
|
|
88
|
+
headers,
|
|
89
|
+
body,
|
|
90
|
+
});
|
|
91
|
+
} catch (err) {
|
|
92
|
+
throw new NetworkError({
|
|
93
|
+
message: err instanceof Error ? err.message : "network request failed",
|
|
94
|
+
code: null,
|
|
95
|
+
requestId: null,
|
|
96
|
+
body: err,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (!res.ok) throw await toApiError(res);
|
|
100
|
+
const expect = opts.expect ?? "json";
|
|
101
|
+
if (expect === "empty") return undefined as T;
|
|
102
|
+
if (expect === "bytes") return new Uint8Array(await res.arrayBuffer()) as T;
|
|
103
|
+
if (expect === "stream") return res.body as T;
|
|
104
|
+
return (await res.json()) as T;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async stream(opts: {
|
|
108
|
+
path: string;
|
|
109
|
+
query?: Record<string, unknown>;
|
|
110
|
+
}): Promise<Response> {
|
|
111
|
+
const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
|
|
112
|
+
let res: Response;
|
|
113
|
+
try {
|
|
114
|
+
res = await this.fetchImpl(url, {
|
|
115
|
+
method: "GET",
|
|
116
|
+
headers: this.headers({ Accept: "text/event-stream" }),
|
|
117
|
+
});
|
|
118
|
+
} catch (err) {
|
|
119
|
+
throw new NetworkError({
|
|
120
|
+
message: err instanceof Error ? err.message : "network request failed",
|
|
121
|
+
code: null,
|
|
122
|
+
requestId: null,
|
|
123
|
+
body: err,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (!res.ok) throw await toApiError(res);
|
|
127
|
+
return res;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function toApiError(res: Response): Promise<IntrospectionAPIError> {
|
|
132
|
+
let body: unknown = undefined;
|
|
133
|
+
let message = `HTTP ${res.status}`;
|
|
134
|
+
let code: string | null = null;
|
|
135
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
136
|
+
if (ct.includes("json")) {
|
|
137
|
+
body = await res.json().catch(() => undefined);
|
|
138
|
+
if (body && typeof body === "object") {
|
|
139
|
+
const obj = body as Record<string, unknown>;
|
|
140
|
+
if (typeof obj.detail === "string") message = obj.detail;
|
|
141
|
+
if (typeof obj.code === "string") code = obj.code;
|
|
142
|
+
if (typeof obj.message === "string" && message === `HTTP ${res.status}`) {
|
|
143
|
+
message = obj.message;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
body = await res.text().catch(() => undefined);
|
|
148
|
+
}
|
|
149
|
+
const retryAfterHeader = res.headers.get("retry-after");
|
|
150
|
+
let retryAfter: number | null = null;
|
|
151
|
+
if (retryAfterHeader) {
|
|
152
|
+
const n = Number(retryAfterHeader);
|
|
153
|
+
if (Number.isFinite(n)) retryAfter = n;
|
|
154
|
+
}
|
|
155
|
+
return apiErrorFromResponse({
|
|
156
|
+
status: res.status,
|
|
157
|
+
message,
|
|
158
|
+
code,
|
|
159
|
+
requestId: res.headers.get("x-request-id"),
|
|
160
|
+
body,
|
|
161
|
+
retryAfter,
|
|
162
|
+
});
|
|
163
|
+
}
|