@capekai/core 1.0.5 → 1.0.7

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.5",
3
+ "version": "1.0.7",
4
4
  "description": "Bun-native composable agent runtime and framework for Capek.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -78,7 +78,7 @@
78
78
  "@ai-sdk/deepseek": "^2.0.35",
79
79
  "@ai-sdk/openai": "^3.0.84",
80
80
  "@capekai/tool": "^1.0.3",
81
- "@capekai/types": "^1.0.2",
81
+ "@capekai/types": "^1.0.3",
82
82
  "@openrouter/ai-sdk-provider": "^2.3.3",
83
83
  "@zip.js/zip.js": "^2.7.60",
84
84
  "ai": "^6.0.116",
@@ -8,6 +8,7 @@ import {
8
8
  type Tool,
9
9
  } from 'ai';
10
10
  import { getModelWithMetadata } from '../core/model-utils';
11
+ import { openAiModelOmitsTemperature } from '../core/provider-utils';
11
12
  import type { ModelFactoryResult } from '../providers/types';
12
13
 
13
14
  export interface TextModelRequest {
@@ -21,7 +22,7 @@ export interface TextModelRequest {
21
22
  }
22
23
 
23
24
  export async function runTextModel(request: TextModelRequest): Promise<string> {
24
- const { model, omitMaxOutputTokens, providerOptions, useProviderInstructions } = await getModelWithMetadata({
25
+ const { model, omitMaxOutputTokens, omitTemperature, providerOptions, useProviderInstructions } = await getModelWithMetadata({
25
26
  modelId: request.modelId,
26
27
  providerId: request.providerId,
27
28
  systemPrompt: request.systemPrompt,
@@ -34,7 +35,7 @@ export async function runTextModel(request: TextModelRequest): Promise<string> {
34
35
  ...(omitMaxOutputTokens || request.maxOutputTokens === undefined
35
36
  ? {}
36
37
  : { maxOutputTokens: request.maxOutputTokens }),
37
- temperature: request.temperature,
38
+ ...(omitTemperature ? {} : { temperature: request.temperature }),
38
39
  providerOptions: providerOptions as Parameters<typeof streamText>[0]['providerOptions'],
39
40
  });
40
41
  return stream.text;
@@ -57,6 +58,7 @@ export function createOpenAiResponsesModel(request: OpenAiResponsesModelRequest)
57
58
  model: openai.responses(request.modelId) as unknown as LanguageModel,
58
59
  useProviderInstructions: true,
59
60
  omitMaxOutputTokens: true,
61
+ omitTemperature: openAiModelOmitsTemperature(request.modelId),
60
62
  providerOptions: {
61
63
  openai: {
62
64
  instructions: request.systemPrompt || 'You are a helpful assistant.',
package/src/core/agent.ts CHANGED
@@ -121,6 +121,7 @@ export async function* streamChat(options: ChatOptions): AsyncGenerator<MessageE
121
121
  broadcastFn: options.broadcastFn,
122
122
  additionalPaths: effectiveAdditionalPaths,
123
123
  agentId: preconfig.id,
124
+ workspaceRootId: session?.workspaceRootId ?? undefined,
124
125
  });
125
126
 
126
127
  const selfDelegationAvailable = Boolean(aiTools.task)
@@ -140,7 +141,7 @@ export async function* streamChat(options: ChatOptions): AsyncGenerator<MessageE
140
141
  selfDelegationAvailable,
141
142
  });
142
143
 
143
- const { model, useProviderInstructions, omitMaxOutputTokens, providerOptions: baseProviderOptions } =
144
+ const { model, useProviderInstructions, omitMaxOutputTokens, omitTemperature, providerOptions: baseProviderOptions } =
144
145
  await getModelWithMetadata({
145
146
  modelId: resolvedModelId,
146
147
  providerId,
@@ -198,7 +199,7 @@ export async function* streamChat(options: ChatOptions): AsyncGenerator<MessageE
198
199
  tools: aiTools,
199
200
  maxOutputTokens: omitMaxOutputTokens ? undefined : getMaxOutputTokens(resolvedModelId),
200
201
  providerOptions: streamConfig.providerOptions as Parameters<typeof streamText>[0]['providerOptions'],
201
- temperature: streamConfig.temperature,
202
+ ...(omitTemperature ? {} : { temperature: streamConfig.temperature }),
202
203
  stopWhen: stepCountIs(streamConfig.maxSteps),
203
204
  abortSignal: abortController.signal,
204
205
  experimental_onStepStart,
@@ -33,6 +33,7 @@ export interface BuildToolsOptions {
33
33
  broadcastFn?: AskBroadcastFn;
34
34
  additionalPaths?: string[];
35
35
  agentId?: string | null;
36
+ workspaceRootId?: string;
36
37
  }
37
38
 
38
39
  export async function buildAiSdkTools(
@@ -53,6 +54,7 @@ export async function buildAiSdkTools(
53
54
  broadcastFn,
54
55
  additionalPaths,
55
56
  agentId,
57
+ workspaceRootId,
56
58
  } = options;
57
59
 
58
60
  // Resolve root session ID by walking up the parent chain
@@ -92,6 +94,7 @@ export async function buildAiSdkTools(
92
94
  modelId,
93
95
  providerId,
94
96
  additionalPaths,
97
+ workspaceRootId,
95
98
  });
96
99
  const tools: ToolMap = { ...externalTools };
97
100
 
@@ -6,6 +6,7 @@ import {
6
6
  isDefaultSessionTitle,
7
7
  isSandboxActive,
8
8
  emitTerminal,
9
+ resolveSessionWorkspace,
9
10
  } from '../runtime/host-dependencies';
10
11
  import { getDefaultPreconfig, getPreconfigOrAgent } from '../context';
11
12
  import {
@@ -552,8 +553,15 @@ export async function handleChat<Origin>(
552
553
  }
553
554
 
554
555
  const workspace = session.workspaceId ? await getWorkspace(session.workspaceId) : null;
555
- const workspacePath = workspace?.path;
556
- const additionalPaths = workspace?.additionalPaths;
556
+ const workspaceContext = await resolveSessionWorkspace({
557
+ sessionId,
558
+ workspaceId: session.workspaceId || undefined,
559
+ workspaceRootId: session.workspaceRootId ?? undefined,
560
+ workspacePath: workspace?.path,
561
+ additionalPaths: workspace?.additionalPaths,
562
+ });
563
+ const workspacePath = workspaceContext.workspacePath;
564
+ const additionalPaths = workspaceContext.additionalPaths;
557
565
 
558
566
  const preconfig = session.preconfigId
559
567
  ? await getPreconfigOrAgent(session.preconfigId)
@@ -810,8 +818,15 @@ export async function handleSessionEditMessage<Origin>(
810
818
  });
811
819
 
812
820
  const workspace = session.workspaceId ? await getWorkspace(session.workspaceId) : null;
813
- const workspacePath = workspace?.path;
814
- const additionalPaths = workspace?.additionalPaths;
821
+ const workspaceContext = await resolveSessionWorkspace({
822
+ sessionId: msg.sessionId,
823
+ workspaceId: session.workspaceId || undefined,
824
+ workspaceRootId: session.workspaceRootId ?? undefined,
825
+ workspacePath: workspace?.path,
826
+ additionalPaths: workspace?.additionalPaths,
827
+ });
828
+ const workspacePath = workspaceContext.workspacePath;
829
+ const additionalPaths = workspaceContext.additionalPaths;
815
830
 
816
831
  const preconfig = session.preconfigId
817
832
  ? await getPreconfigOrAgent(session.preconfigId)
@@ -1,6 +1,6 @@
1
1
  import { type LanguageModel } from 'ai';
2
2
  import { createOpenAI } from '@ai-sdk/openai';
3
- import { findProviderFromModel, parseModelSpecifier } from './provider-utils';
3
+ import { findProviderFromModel, openAiModelOmitsTemperature, parseModelSpecifier } from './provider-utils';
4
4
  import { findModel, getApiKeyForProvider, getLLMBaseUrl, getModelsConfig } from '../configuration/runtime';
5
5
  import { createModelForProvider, getProvider } from '../providers/registry';
6
6
  import { isSandboxActive } from '../runtime/host-dependencies';
@@ -9,6 +9,7 @@ export interface ModelWithMetadata {
9
9
  model: LanguageModel;
10
10
  useProviderInstructions?: boolean;
11
11
  omitMaxOutputTokens?: boolean;
12
+ omitTemperature?: boolean;
12
13
  providerOptions?: Record<string, Record<string, unknown>>;
13
14
  }
14
15
 
@@ -46,6 +47,7 @@ export async function getModelWithMetadata(
46
47
  model: result.model,
47
48
  useProviderInstructions: result.useProviderInstructions,
48
49
  omitMaxOutputTokens: result.omitMaxOutputTokens,
50
+ omitTemperature: result.omitTemperature,
49
51
  providerOptions: result.providerOptions,
50
52
  };
51
53
  }
@@ -77,6 +79,7 @@ export async function getModelWithMetadata(
77
79
  model: result.model,
78
80
  useProviderInstructions: result.useProviderInstructions,
79
81
  omitMaxOutputTokens: result.omitMaxOutputTokens,
82
+ omitTemperature: result.omitTemperature,
80
83
  providerOptions: result.providerOptions,
81
84
  };
82
85
  }
@@ -132,6 +135,7 @@ export async function getModelWithMetadata(
132
135
  });
133
136
  return {
134
137
  model: openai.responses(model) as unknown as LanguageModel,
138
+ omitTemperature: openAiModelOmitsTemperature(model),
135
139
  providerOptions: {
136
140
  openai: {
137
141
  promptCacheKey: options.sessionId,
@@ -19,6 +19,10 @@ const PROVIDER_PREFIXES: Array<{ test: (m: string) => boolean; provider: string
19
19
  { test: (m) => m.startsWith('deepseek-'), provider: 'deepseek' },
20
20
  ];
21
21
 
22
+ export function openAiModelOmitsTemperature(modelId: string): boolean {
23
+ return modelId.startsWith('gpt-6');
24
+ }
25
+
22
26
  export interface ParsedModelSpecifier {
23
27
  modelId: string;
24
28
  providerId?: string;
@@ -19,7 +19,7 @@ import {
19
19
  } from '../../runtime/domain-tool-source';
20
20
  import { isToolAllowedInContext, type ToolExecutionScope } from '../tool-capabilities';
21
21
  import type { ToolMap } from './types';
22
- import type { BroadcastFn } from '../../runtime/host-dependencies';
22
+ import { resolveToolDefinition, type BroadcastFn } from '../../runtime/host-dependencies';
23
23
  import {
24
24
  capekToolOutputToAiSdk,
25
25
  createCapekToolOutputEnvelope,
@@ -39,6 +39,7 @@ export interface ExternalToolsOptions {
39
39
  modelId?: string;
40
40
  providerId?: string;
41
41
  additionalPaths?: string[];
42
+ workspaceRootId?: string;
42
43
  }
43
44
 
44
45
  export async function buildExternalTools(options: ExternalToolsOptions): Promise<ToolMap> {
@@ -56,6 +57,7 @@ export async function buildExternalTools(options: ExternalToolsOptions): Promise
56
57
  modelId,
57
58
  providerId,
58
59
  additionalPaths,
60
+ workspaceRootId,
59
61
  } = options;
60
62
 
61
63
  const tools: ToolMap = {};
@@ -83,7 +85,14 @@ export async function buildExternalTools(options: ExternalToolsOptions): Promise
83
85
  const loadedTool = await getTool(name);
84
86
  if (!loadedTool) continue;
85
87
 
86
- const { definition } = loadedTool;
88
+ const definition = await resolveToolDefinition({
89
+ sessionId,
90
+ workspaceId,
91
+ workspaceRootId,
92
+ workspacePath,
93
+ definition: loadedTool.definition,
94
+ });
95
+ if (!definition) continue;
87
96
 
88
97
  if (!isToolAllowedInContext(definition.capabilities, executionScopes)) {
89
98
  continue;
@@ -22,7 +22,9 @@ export {
22
22
  type PermissionRequestStatus,
23
23
  type RuntimeHost,
24
24
  type SandboxBindings,
25
+ type SessionWorkspaceContext,
25
26
  type TitleHost,
27
+ type ToolPolicyHost,
26
28
  type WorkspaceCapabilityBindings,
27
29
  } from '../runtime/host';
28
30
  export { createStandaloneHost } from '../runtime/standalone-host';
@@ -12,6 +12,7 @@ export interface ModelFactoryResult {
12
12
  model: LanguageModel;
13
13
  useProviderInstructions?: boolean;
14
14
  omitMaxOutputTokens?: boolean;
15
+ omitTemperature?: boolean;
15
16
  providerOptions?: Record<string, Record<string, unknown>>;
16
17
  }
17
18
 
@@ -1,5 +1,6 @@
1
1
  import type { Session } from '@capekai/types';
2
- import { getRuntimeHost } from './host';
2
+ import type { ToolDefinition } from '@capekai/tool';
3
+ import { getOptionalRuntimeHost, getRuntimeHost } from './host';
3
4
  import type { RuntimeAudience, RuntimeDelivery, RuntimeEvent } from './events';
4
5
 
5
6
  type HostRuntimeAudience = Exclude<RuntimeAudience, { scope: 'origin' }>;
@@ -66,6 +67,38 @@ export const generateSessionTitle = (...args: Parameters<ReturnType<typeof getRu
66
67
  getRuntimeHost().titles.generateSessionTitle(...args);
67
68
  export const getToolWorkspaceHost = (...args: Parameters<ReturnType<typeof getRuntimeHost>['workspace']['createToolWorkspaceHost']>) =>
68
69
  getRuntimeHost().workspace.createToolWorkspaceHost(...args);
70
+
71
+ export async function resolveSessionWorkspace(options: {
72
+ sessionId: string;
73
+ workspaceId?: string;
74
+ workspaceRootId?: string;
75
+ workspacePath?: string;
76
+ additionalPaths?: string[];
77
+ }): Promise<{ workspacePath?: string; additionalPaths?: string[] }> {
78
+ const resolver = getRuntimeHost().workspace.resolveSessionWorkspace;
79
+ if (resolver) {
80
+ return await resolver(options);
81
+ }
82
+ if (options.workspaceRootId) {
83
+ throw new Error('Session workspace root requires a host resolver');
84
+ }
85
+ return {
86
+ workspacePath: options.workspacePath,
87
+ additionalPaths: options.additionalPaths,
88
+ };
89
+ }
90
+
91
+ export async function resolveToolDefinition(options: {
92
+ sessionId: string;
93
+ workspaceId?: string;
94
+ workspaceRootId?: string;
95
+ workspacePath?: string;
96
+ definition: ToolDefinition;
97
+ }): Promise<ToolDefinition | null> {
98
+ const resolver = getOptionalRuntimeHost()?.toolPolicy?.resolveDefinition;
99
+ return resolver ? await resolver(options) : options.definition;
100
+ }
101
+
69
102
  export const isSandboxActive = (): boolean => getRuntimeHost().sandbox.isSandboxActive();
70
103
 
71
104
  export type { RuntimeEventSink, RuntimeEventSink as BroadcastFn } from './events';
@@ -1,6 +1,6 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
2
  import type { HostLayout } from './host-layout';
3
- import type { Ask } from '@capekai/tool';
3
+ import type { Ask, ToolDefinition } from '@capekai/tool';
4
4
  import type {
5
5
  AskRequestMessage, AskTimedOutMessage, AutoApproveSeverity, MessageWithParts, Session,
6
6
  } from '@capekai/types';
@@ -82,7 +82,19 @@ export interface TitleHost {
82
82
  generateSessionTitle(messages: MessageWithParts[]): Promise<string | null>;
83
83
  }
84
84
 
85
+ export interface SessionWorkspaceContext {
86
+ workspacePath?: string;
87
+ additionalPaths?: string[];
88
+ }
89
+
85
90
  export interface WorkspaceCapabilityBindings {
91
+ resolveSessionWorkspace?(options: {
92
+ sessionId: string;
93
+ workspaceId?: string;
94
+ workspaceRootId?: string;
95
+ workspacePath?: string;
96
+ additionalPaths?: string[];
97
+ }): SessionWorkspaceContext | Promise<SessionWorkspaceContext>;
86
98
  createToolWorkspaceHost(options: {
87
99
  workspaceId?: string;
88
100
  workspacePath?: string;
@@ -91,6 +103,16 @@ export interface WorkspaceCapabilityBindings {
91
103
  }): WorkspaceCapabilityHost;
92
104
  }
93
105
 
106
+ export interface ToolPolicyHost {
107
+ resolveDefinition?(options: {
108
+ sessionId: string;
109
+ workspaceId?: string;
110
+ workspaceRootId?: string;
111
+ workspacePath?: string;
112
+ definition: ToolDefinition;
113
+ }): ToolDefinition | null | Promise<ToolDefinition | null>;
114
+ }
115
+
94
116
  export interface SandboxBindings {
95
117
  isSandboxActive(): boolean;
96
118
  }
@@ -100,6 +122,7 @@ export interface RuntimeHost {
100
122
  delivery: DeliveryHost;
101
123
  titles: TitleHost;
102
124
  workspace: WorkspaceCapabilityBindings;
125
+ toolPolicy?: ToolPolicyHost;
103
126
  sandbox: SandboxBindings;
104
127
  /** Host-supplied filesystem layout policy. */
105
128
  layout?: HostLayout;
@@ -122,8 +145,12 @@ export function withRuntimeHost<T>(value: RuntimeHost, callback: () => T): T {
122
145
  return scopedHost.run(value, callback);
123
146
  }
124
147
 
148
+ export function getOptionalRuntimeHost(): RuntimeHost | null {
149
+ return scopedHost.getStore() ?? host;
150
+ }
151
+
125
152
  export function getRuntimeHost(): RuntimeHost {
126
- const active = scopedHost.getStore() ?? host;
153
+ const active = getOptionalRuntimeHost();
127
154
  if (!active) throw new Error('Runtime host has not been configured');
128
155
  return active;
129
156
  }
@@ -5,6 +5,7 @@ import {
5
5
  emitTerminal,
6
6
  emitToAskTargets,
7
7
  emitToController,
8
+ resolveSessionWorkspace,
8
9
  } from '../runtime/host-dependencies';
9
10
  import { getLLMSubagentMaxSteps } from '../configuration/runtime';
10
11
  import {
@@ -59,9 +60,21 @@ export async function executeChildSession(options: {
59
60
  streamChat = streamChatWithRetry,
60
61
  } = options;
61
62
 
62
- // Resolve additionalPaths from workspace
63
- const workspace = workspaceId ? await getWorkspace(workspaceId) : null;
64
- const additionalPaths = workspace?.additionalPaths;
63
+ // Re-resolve the child session's host-owned root. Async child execution may
64
+ // enter after the parent context was assembled, so the persisted child
65
+ // binding is authoritative.
66
+ const childSession = await getSession(childSessionId);
67
+ const effectiveWorkspaceId = workspaceId ?? childSession?.workspaceId ?? undefined;
68
+ const workspace = effectiveWorkspaceId ? await getWorkspace(effectiveWorkspaceId) : null;
69
+ const workspaceContext = await resolveSessionWorkspace({
70
+ sessionId: childSessionId,
71
+ workspaceId: effectiveWorkspaceId,
72
+ workspaceRootId: childSession?.workspaceRootId ?? undefined,
73
+ workspacePath: workspacePath ?? workspace?.path,
74
+ additionalPaths: workspace?.additionalPaths,
75
+ });
76
+ const effectiveWorkspacePath = workspaceContext.workspacePath;
77
+ const additionalPaths = workspaceContext.additionalPaths;
65
78
 
66
79
  let messages: MessageWithParts[];
67
80
 
@@ -172,8 +185,8 @@ export async function executeChildSession(options: {
172
185
  sessionId: childSessionId,
173
186
  preconfig,
174
187
  messages,
175
- workspacePath,
176
- workspaceId,
188
+ workspacePath: effectiveWorkspacePath,
189
+ workspaceId: effectiveWorkspaceId,
177
190
  additionalPaths,
178
191
  modelId: modelId ?? undefined,
179
192
  providerId: providerId ?? undefined,
@@ -429,6 +429,7 @@ async function runSubagent(
429
429
  childSession = await createSessionFn({
430
430
  id: randomUUID(),
431
431
  workspaceId: workspaceId || parentSession?.workspaceId || '',
432
+ workspaceRootId: parentSession?.workspaceRootId ?? null,
432
433
  preconfigId: subagent_type,
433
434
  title: `${description} (@${subagent_type} subagent)`,
434
435
  status: 'active',