@capekai/core 1.0.4 → 1.0.5
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capekai/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Bun-native composable agent runtime and framework for Capek.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -77,8 +77,8 @@
|
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@ai-sdk/deepseek": "^2.0.35",
|
|
79
79
|
"@ai-sdk/openai": "^3.0.84",
|
|
80
|
-
"@capekai/tool": "^1.0.
|
|
81
|
-
"@capekai/types": "^1.0.
|
|
80
|
+
"@capekai/tool": "^1.0.3",
|
|
81
|
+
"@capekai/types": "^1.0.2",
|
|
82
82
|
"@openrouter/ai-sdk-provider": "^2.3.3",
|
|
83
83
|
"@zip.js/zip.js": "^2.7.60",
|
|
84
84
|
"ai": "^6.0.116",
|
|
@@ -4,6 +4,7 @@ import { isTextPart, isToolPart, isImagePart, isFilePart, parseToolInput } from
|
|
|
4
4
|
import { stripVisualization } from '../utils/strip-visualization';
|
|
5
5
|
import { getAttachment } from '../storage/runtime';
|
|
6
6
|
import { isToolOutputArtifactReference, RETRIEVE_TOOL_OUTPUT_NAME } from '../tool-output/policy';
|
|
7
|
+
import { toolModelOutputToAiSdk } from '../tools/model-output';
|
|
7
8
|
|
|
8
9
|
type AiSdkContent = string | Array<{
|
|
9
10
|
type: 'text' | 'tool-call' | 'tool-result' | 'image' | 'file';
|
|
@@ -121,7 +122,9 @@ export async function convertToAiSdkMessages(
|
|
|
121
122
|
type: 'tool-result' as const,
|
|
122
123
|
toolCallId: toolPart.callId,
|
|
123
124
|
toolName: toolPart.name,
|
|
124
|
-
output:
|
|
125
|
+
output: toolPart.state.modelOutput
|
|
126
|
+
? toolModelOutputToAiSdk(toolPart.state.modelOutput)
|
|
127
|
+
: { type: 'json' as const, value: stripVisualization(toolPart.state.output) },
|
|
125
128
|
});
|
|
126
129
|
}
|
|
127
130
|
} else if (toolPart.state.status === 'error') {
|
|
@@ -2,6 +2,7 @@ import type { TextPart, ToolPart, ReasoningPart, MessageEvent } from '@capekai/t
|
|
|
2
2
|
import { createPart, updatePart, getPart, persistStreamingPartSnapshots } from '../storage/runtime';
|
|
3
3
|
import { parseToolInput } from './part-utils';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
|
+
import { isCapekToolOutputEnvelope } from '../tools/model-output';
|
|
5
6
|
|
|
6
7
|
const STREAM_PART_PERSIST_INTERVAL_MS = 300;
|
|
7
8
|
|
|
@@ -184,8 +185,11 @@ export function createStreamHandlers(ctx: StreamHandlerContext) {
|
|
|
184
185
|
const latestPart = await getPart(existingToolPart.id) as ToolPart | null;
|
|
185
186
|
const latestState = latestPart?.state;
|
|
186
187
|
|
|
188
|
+
const capekOutput = isCapekToolOutputEnvelope(delta.output) ? delta.output : undefined;
|
|
187
189
|
let resultData: unknown;
|
|
188
|
-
if (
|
|
190
|
+
if (capekOutput) {
|
|
191
|
+
resultData = capekOutput.value;
|
|
192
|
+
} else if (typeof delta.output === 'string') {
|
|
189
193
|
try {
|
|
190
194
|
resultData = JSON.parse(delta.output);
|
|
191
195
|
} catch {
|
|
@@ -218,6 +222,7 @@ export function createStreamHandlers(ctx: StreamHandlerContext) {
|
|
|
218
222
|
status: 'completed' as const,
|
|
219
223
|
input: existingToolPart.state.input,
|
|
220
224
|
output: resultData,
|
|
225
|
+
...(capekOutput && { modelOutput: capekOutput.modelOutput }),
|
|
221
226
|
startedAt: Date.now(),
|
|
222
227
|
completedAt: Date.now(),
|
|
223
228
|
...(existingChildSessionId && { childSessionId: existingChildSessionId }),
|
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
import { isToolAllowedInContext, type ToolExecutionScope } from '../tool-capabilities';
|
|
21
21
|
import type { ToolMap } from './types';
|
|
22
22
|
import type { BroadcastFn } from '../../runtime/host-dependencies';
|
|
23
|
+
import {
|
|
24
|
+
capekToolOutputToAiSdk,
|
|
25
|
+
createCapekToolOutputEnvelope,
|
|
26
|
+
} from '../../tools/model-output';
|
|
23
27
|
|
|
24
28
|
export interface ExternalToolsOptions {
|
|
25
29
|
toolNames: string[];
|
|
@@ -88,6 +92,7 @@ export async function buildExternalTools(options: ExternalToolsOptions): Promise
|
|
|
88
92
|
tools[name] = tool({
|
|
89
93
|
description: definition.description,
|
|
90
94
|
inputSchema: jsonSchema(definition.inputSchema),
|
|
95
|
+
toModelOutput: ({ output }) => capekToolOutputToAiSdk(output),
|
|
91
96
|
execute: async (args: Record<string, unknown>, { toolCallId }: { toolCallId: string }) => {
|
|
92
97
|
const toolAbortController = interruptManager.registerToolExecution(sessionId, toolCallId);
|
|
93
98
|
|
|
@@ -121,11 +126,10 @@ export async function buildExternalTools(options: ExternalToolsOptions): Promise
|
|
|
121
126
|
return { error: result.error ?? 'Tool execution failed' };
|
|
122
127
|
}
|
|
123
128
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return result.result;
|
|
129
|
+
const clientResult = result.visualization && result.result && typeof result.result === 'object'
|
|
130
|
+
? { ...result.result as Record<string, unknown>, _visualization: result.visualization }
|
|
131
|
+
: result.result;
|
|
132
|
+
return createCapekToolOutputEnvelope(clientResult, result.modelOutput);
|
|
129
133
|
} finally {
|
|
130
134
|
interruptManager.unregisterToolExecution(sessionId, toolCallId);
|
|
131
135
|
await rejectPendingAsksByToolCallId(toolCallId);
|
|
@@ -34,6 +34,10 @@ import type {
|
|
|
34
34
|
ToolOutputPolicyContext,
|
|
35
35
|
ToolOutputPolicyOptions,
|
|
36
36
|
} from './contracts';
|
|
37
|
+
import {
|
|
38
|
+
isCapekToolOutputEnvelope,
|
|
39
|
+
type CapekToolOutputEnvelope,
|
|
40
|
+
} from '../tools/model-output';
|
|
37
41
|
|
|
38
42
|
export const TOOL_OUTPUT_THRESHOLD_CHARS = 50_000;
|
|
39
43
|
export const TOOL_OUTPUT_PREVIEW_CHARS = 10_000;
|
|
@@ -116,6 +120,13 @@ export function createToolOutputService(
|
|
|
116
120
|
const outputPolicyWrappedTools = new WeakSet<object>();
|
|
117
121
|
|
|
118
122
|
async function applyToolOutputPolicy(result: unknown, context: ToolOutputPolicyContext): Promise<unknown> {
|
|
123
|
+
if (isCapekToolOutputEnvelope(result)) {
|
|
124
|
+
return {
|
|
125
|
+
...result,
|
|
126
|
+
value: await applyToolOutputPolicy(result.value, context),
|
|
127
|
+
} satisfies CapekToolOutputEnvelope;
|
|
128
|
+
}
|
|
129
|
+
|
|
119
130
|
const visualization = result && typeof result === 'object' && !Array.isArray(result)
|
|
120
131
|
? (result as Record<string, unknown>)._visualization
|
|
121
132
|
: undefined;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { JSONValue, Tool } from 'ai';
|
|
2
|
+
import type { ToolModelOutputPart } from '@capekai/types';
|
|
3
|
+
|
|
4
|
+
type AiSdkToolResultOutput = Awaited<ReturnType<NonNullable<Tool['toModelOutput']>>>;
|
|
5
|
+
|
|
6
|
+
const CAPEK_TOOL_OUTPUT_TYPE = 'capek-tool-output';
|
|
7
|
+
|
|
8
|
+
export interface CapekToolOutputEnvelope {
|
|
9
|
+
type: typeof CAPEK_TOOL_OUTPUT_TYPE;
|
|
10
|
+
value: unknown;
|
|
11
|
+
modelOutput: ToolModelOutputPart[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isToolModelOutputPart(value: unknown): value is ToolModelOutputPart {
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
16
|
+
const part = value as Record<string, unknown>;
|
|
17
|
+
if (part.type === 'text') {
|
|
18
|
+
return typeof part.text === 'string';
|
|
19
|
+
}
|
|
20
|
+
if (part.type === 'image') {
|
|
21
|
+
return typeof part.data === 'string'
|
|
22
|
+
&& part.data.length > 0
|
|
23
|
+
&& !part.data.startsWith('data:')
|
|
24
|
+
&& typeof part.mediaType === 'string'
|
|
25
|
+
&& part.mediaType.startsWith('image/');
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeToolModelOutput(value: unknown): ToolModelOutputPart[] | undefined {
|
|
31
|
+
if (!Array.isArray(value) || value.length === 0 || !value.every(isToolModelOutputPart)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createCapekToolOutputEnvelope(
|
|
38
|
+
value: unknown,
|
|
39
|
+
modelOutput: unknown,
|
|
40
|
+
): unknown {
|
|
41
|
+
const normalized = normalizeToolModelOutput(modelOutput);
|
|
42
|
+
return normalized
|
|
43
|
+
? { type: CAPEK_TOOL_OUTPUT_TYPE, value, modelOutput: normalized }
|
|
44
|
+
: value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isCapekToolOutputEnvelope(value: unknown): value is CapekToolOutputEnvelope {
|
|
48
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
49
|
+
const record = value as Record<string, unknown>;
|
|
50
|
+
return record.type === CAPEK_TOOL_OUTPUT_TYPE
|
|
51
|
+
&& 'value' in record
|
|
52
|
+
&& normalizeToolModelOutput(record.modelOutput) !== undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function toolModelOutputToAiSdk(parts: ToolModelOutputPart[]): AiSdkToolResultOutput {
|
|
56
|
+
return {
|
|
57
|
+
type: 'content',
|
|
58
|
+
value: parts.map((part) => part.type === 'text'
|
|
59
|
+
? { type: 'text' as const, text: part.text }
|
|
60
|
+
: {
|
|
61
|
+
type: 'image-data' as const,
|
|
62
|
+
data: part.data,
|
|
63
|
+
mediaType: part.mediaType,
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function capekToolOutputToAiSdk(output: unknown): AiSdkToolResultOutput {
|
|
69
|
+
if (isCapekToolOutputEnvelope(output)) {
|
|
70
|
+
return toolModelOutputToAiSdk(output.modelOutput);
|
|
71
|
+
}
|
|
72
|
+
return typeof output === 'string'
|
|
73
|
+
? { type: 'text', value: output }
|
|
74
|
+
: { type: 'json', value: (output ?? null) as JSONValue };
|
|
75
|
+
}
|