@capekai/core 1.0.9 → 1.0.11

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.9",
3
+ "version": "1.0.11",
4
4
  "description": "Bun-native composable agent runtime and framework for Capek.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -75,8 +75,9 @@
75
75
  "access": "public"
76
76
  },
77
77
  "dependencies": {
78
- "@ai-sdk/deepseek": "^2.0.35",
78
+ "@ai-sdk/anthropic": "^3.0.116",
79
79
  "@ai-sdk/openai": "^3.0.84",
80
+ "@ai-sdk/openai-compatible": "^2.0.74",
80
81
  "@capekai/tool": "^1.0.3",
81
82
  "@capekai/types": "^1.0.3",
82
83
  "@openrouter/ai-sdk-provider": "^2.3.3",
@@ -84,9 +85,7 @@
84
85
  "ai": "^6.0.116",
85
86
  "ignore": "^7.0.3",
86
87
  "picomatch": "^4.0.2",
87
- "tar": "^7.5.13",
88
- "vercel-minimax-ai-provider": "^0.0.2",
89
- "zhipu-ai-provider": "^0.2.2"
88
+ "tar": "^7.5.13"
90
89
  },
91
90
  "scripts": {
92
91
  "build": "tsc --noEmit",
@@ -1,15 +1,16 @@
1
1
  import { createOpenAI } from '@ai-sdk/openai';
2
2
  import {
3
+ wrapLanguageModel,
3
4
  dynamicTool,
4
5
  jsonSchema,
5
6
  streamText,
6
7
  type JSONSchema7,
7
- type LanguageModel,
8
8
  type Tool,
9
9
  } from 'ai';
10
10
  import { getModelWithMetadata } from '../core/model-utils';
11
11
  import { openAiModelOmitsTemperature } from '../core/provider-utils';
12
12
  import type { ModelFactoryResult } from '../providers/types';
13
+ import { codexNetworkRetryMiddleware } from './codex-network-retry';
13
14
 
14
15
  export interface TextModelRequest {
15
16
  modelId?: string;
@@ -55,7 +56,10 @@ export function createOpenAiResponsesModel(request: OpenAiResponsesModelRequest)
55
56
  fetch: request.fetch,
56
57
  });
57
58
  return {
58
- model: openai.responses(request.modelId) as unknown as LanguageModel,
59
+ model: wrapLanguageModel({
60
+ model: openai.responses(request.modelId),
61
+ middleware: codexNetworkRetryMiddleware,
62
+ }),
59
63
  useProviderInstructions: true,
60
64
  omitMaxOutputTokens: true,
61
65
  omitTemperature: openAiModelOmitsTemperature(request.modelId),
@@ -0,0 +1,25 @@
1
+ import { APICallError, type LanguageModelMiddleware } from 'ai';
2
+ import { ApiErrorType, classifyApiError } from '../utils/errors';
3
+
4
+ export const codexNetworkRetryMiddleware: LanguageModelMiddleware = {
5
+ specificationVersion: 'v3',
6
+ wrapStream: async ({ doStream, params }) => {
7
+ try {
8
+ return await doStream();
9
+ } catch (error: unknown) {
10
+ if (params.abortSignal?.aborted || APICallError.isInstance(error)
11
+ || (error instanceof Error && error.name === 'AbortError')) throw error;
12
+ const classified = classifyApiError(error);
13
+ if (classified.type !== ApiErrorType.Network) throw error;
14
+ // OpenAI's early stream probe can throw a raw socket error. Normalize
15
+ // only before stream handoff so SDK retries this request, not prior tools.
16
+ throw new APICallError({
17
+ message: classified.message,
18
+ url: 'https://chatgpt.com/backend-api/codex/responses',
19
+ requestBodyValues: undefined,
20
+ cause: error,
21
+ isRetryable: true,
22
+ });
23
+ }
24
+ },
25
+ };
package/src/core/agent.ts CHANGED
@@ -144,7 +144,7 @@ export async function* streamChat(options: ChatOptions): AsyncGenerator<(Message
144
144
  selfDelegationAvailable,
145
145
  });
146
146
 
147
- const { model, useProviderInstructions, omitMaxOutputTokens, omitTemperature, providerOptions: baseProviderOptions } =
147
+ const { model, replayReasoning, useProviderInstructions, omitMaxOutputTokens, omitTemperature, providerOptions: baseProviderOptions } =
148
148
  await getModelWithMetadata({
149
149
  modelId: resolvedModelId,
150
150
  providerId,
@@ -157,7 +157,7 @@ export async function* streamChat(options: ChatOptions): AsyncGenerator<(Message
157
157
 
158
158
  // Convert messages for ai-sdk
159
159
  const modelDef = resolvedModelId ? findModel(resolvedModelId) : undefined;
160
- const aiMessages = await convertToAiSdkMessages(messages, modelDef?.capabilities);
160
+ const aiMessages = await convertToAiSdkMessages(messages, modelDef?.capabilities, { replayReasoning });
161
161
  if (options.continueFromCompaction) {
162
162
  // Execution instruction only: do not persist it as another user request.
163
163
  aiMessages.push({ role: 'user', content: 'Continue the existing task from the checkpoint above. Preserve completed work and tool outcomes; do not restart or repeat completed actions. Follow the remaining steps, or report completion if nothing remains.' });
@@ -47,6 +47,7 @@ async function resolveAttachmentPath(part: ImagePart | FilePart): Promise<{ abso
47
47
  export async function convertToAiSdkMessages(
48
48
  messages: MessageWithParts[],
49
49
  modelCapabilities?: ModelCapabilities,
50
+ options: { replayReasoning?: boolean } = {},
50
51
  ): Promise<ModelMessage[]> {
51
52
  const result: { role: 'user' | 'assistant' | 'system' | 'tool'; content: AiSdkContent }[] = [];
52
53
 
@@ -54,6 +55,11 @@ export async function convertToAiSdkMessages(
54
55
  const msg = msgWithParts.message;
55
56
  const parts = msgWithParts.parts;
56
57
 
58
+ const reasoningOptions = options.replayReasoning && msg.role === 'assistant'
59
+ ? { providerOptions: { openaiCompatible: {
60
+ reasoning_content: parts.filter((part) => part.type === 'reasoning').map((part) => part.text).join(''),
61
+ } } }
62
+ : {};
57
63
  const textBlocks: string[] = [];
58
64
  const toolCallBlocks: Array<{
59
65
  type: 'tool-call';
@@ -243,6 +249,7 @@ export async function convertToAiSdkMessages(
243
249
  result.push({
244
250
  role: hasCompactionTrigger ? 'user' : (msg.role as 'user' | 'assistant' | 'system'),
245
251
  content,
252
+ ...(!hasCompactionTrigger ? reasoningOptions : {}),
246
253
  });
247
254
  continue;
248
255
  }
@@ -250,6 +257,7 @@ export async function convertToAiSdkMessages(
250
257
  result.push({
251
258
  role: msg.role as 'user' | 'assistant' | 'system',
252
259
  content: contentParts,
260
+ ...reasoningOptions,
253
261
  });
254
262
 
255
263
  for (const toolResult of toolResultBlocks) {
@@ -7,6 +7,8 @@ import { isSandboxActive } from '../runtime/host-dependencies';
7
7
 
8
8
  export interface ModelWithMetadata {
9
9
  model: LanguageModel;
10
+ /** Replay stored reasoning only for adapters that accept unsigned reasoning text. */
11
+ replayReasoning?: boolean;
10
12
  useProviderInstructions?: boolean;
11
13
  omitMaxOutputTokens?: boolean;
12
14
  omitTemperature?: boolean;
@@ -97,34 +99,16 @@ export async function getModelWithMetadata(
97
99
  return { model: openrouter.chat(model) as unknown as LanguageModel };
98
100
  }
99
101
 
100
- case 'minimax': {
101
- const { createMinimax } = await import('vercel-minimax-ai-provider');
102
- const minimax = createMinimax({ apiKey });
103
- return { model: minimax.chat(model) as unknown as LanguageModel };
104
- }
105
-
106
- case 'zhipu': {
107
- const { createZhipu } = await import('zhipu-ai-provider');
108
- const zhipu = createZhipu({
109
- apiKey,
110
- baseURL: 'https://open.bigmodel.cn/api/paas/v4',
111
- });
112
- return { model: zhipu.chat(model) as unknown as LanguageModel };
113
- }
114
-
102
+ case 'minimax':
103
+ case 'zhipu':
115
104
  case 'zhipu-coding': {
116
- const { createZhipu } = await import('zhipu-ai-provider');
117
- const zhipu = createZhipu({
118
- apiKey,
119
- baseURL: 'https://api.z.ai/api/coding/paas/v4',
120
- });
121
- return { model: zhipu.chat(model) as unknown as LanguageModel };
105
+ const { createProtocolModel } = await import('../providers/protocol-model');
106
+ return { model: createProtocolModel(provider, model, apiKey) };
122
107
  }
123
108
 
124
109
  case 'deepseek': {
125
- const { createDeepSeek } = await import('@ai-sdk/deepseek');
126
- const deepseek = createDeepSeek({ apiKey });
127
- return { model: deepseek.chat(model) as unknown as LanguageModel };
110
+ const { createProtocolModel } = await import('../providers/protocol-model');
111
+ return { model: createProtocolModel(provider, model, apiKey), replayReasoning: true };
128
112
  }
129
113
 
130
114
  case 'openai':
@@ -43,7 +43,11 @@ export function buildStreamConfig(options: StreamConfigOptions): StreamConfigRes
43
43
  // Determine the provider-specific providerOptions key
44
44
  const resolvedProvider = providerId || getModelsConfig().defaultProvider;
45
45
  const registered = resolvedProvider ? getProvider(resolvedProvider) : undefined;
46
- const providerOptionsKey = registered?.descriptor.providerOptionsKey ?? resolvedProvider;
46
+ // Built-in MiniMax uses Anthropic's option namespace. Registered overrides
47
+ // retain their own namespace unless their descriptor explicitly changes it.
48
+ const providerOptionsKey = registered
49
+ ? registered.descriptor.providerOptionsKey ?? resolvedProvider
50
+ : resolvedProvider === 'minimax' ? 'anthropic' : resolvedProvider;
47
51
 
48
52
  // Build merged providerOptions
49
53
  let providerOptions: Record<string, Record<string, unknown>> | undefined;
@@ -0,0 +1,54 @@
1
+ import { createAnthropic } from '@ai-sdk/anthropic';
2
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
3
+ import { wrapLanguageModel, type LanguageModel } from 'ai';
4
+
5
+ export type ProtocolProvider = 'minimax' | 'zhipu' | 'zhipu-coding' | 'deepseek';
6
+
7
+ export function createProtocolModel(
8
+ providerId: ProtocolProvider,
9
+ modelId: string,
10
+ apiKey: string,
11
+ fetch?: NonNullable<Parameters<typeof createAnthropic>[0]>['fetch'],
12
+ ): LanguageModel {
13
+ if (providerId === 'minimax') {
14
+ return createAnthropic({ apiKey, baseURL: 'https://api.minimax.io/anthropic/v1', fetch })(modelId);
15
+ }
16
+ const model = createOpenAICompatible({
17
+ name: providerId,
18
+ apiKey,
19
+ baseURL: providerId === 'deepseek' ? 'https://api.deepseek.com/v1' : providerId === 'zhipu'
20
+ ? 'https://open.bigmodel.cn/api/paas/v4'
21
+ : 'https://api.z.ai/api/coding/paas/v4',
22
+ fetch,
23
+ })(modelId);
24
+ if (providerId !== 'deepseek') return model;
25
+ return wrapLanguageModel({
26
+ model,
27
+ middleware: {
28
+ specificationVersion: 'v3',
29
+ transformParams: async ({ params }) => ({
30
+ ...params,
31
+ prompt: params.prompt.map((message) => {
32
+ if (message.role !== 'assistant') return message;
33
+ const reasoning = message.content.filter((part) => part.type === 'reasoning');
34
+ const existing = message.providerOptions?.openaiCompatible?.reasoning_content;
35
+ return {
36
+ ...message,
37
+ content: message.content.filter((part) => part.type !== 'reasoning'),
38
+ providerOptions: {
39
+ ...message.providerOptions,
40
+ openaiCompatible: {
41
+ ...message.providerOptions?.openaiCompatible,
42
+ // The SDK omits empty reasoning by default. DeepSeek needs the field
43
+ // on tool continuations too, including SDK-generated assistant steps.
44
+ reasoning_content: reasoning.length > 0
45
+ ? reasoning.map((part) => part.text).join('')
46
+ : typeof existing === 'string' ? existing : '',
47
+ },
48
+ },
49
+ };
50
+ }),
51
+ }),
52
+ },
53
+ });
54
+ }