@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.4",
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.2",
81
- "@capekai/types": "^1.0.1",
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: { type: 'json' as const, value: stripVisualization(toolPart.state.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 (typeof delta.output === 'string') {
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
- if (result.visualization && result.result && typeof result.result === 'object') {
125
- return { ...result.result as Record<string, unknown>, _visualization: result.visualization };
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
+ }