crewly 1.11.6 → 1.12.1
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/config/skills/agent/onboarding/synthesize-hierarchy/SKILL.md +65 -0
- package/config/skills/agent/onboarding/synthesize-hierarchy/execute.sh +61 -0
- package/config/skills/agent/web-search/SKILL.md +70 -0
- package/config/skills/agent/web-search/execute.sh +170 -0
- package/config/skills/agent/web-search/skill.json +23 -0
- package/dist/backend/backend/src/constants.d.ts +12 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +12 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts +22 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js +58 -0
- package/dist/backend/backend/src/controllers/cloud/cloud.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js +3 -1
- package/dist/backend/backend/src/controllers/cloud/cloud.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.d.ts +27 -0
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.js +108 -0
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.d.ts +6 -2
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.js +9 -3
- package/dist/backend/backend/src/controllers/orchestrator-onboarding/orchestrator-onboarding.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +36 -2
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +18 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +24 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts +102 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +167 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -0
- package/dist/backend/backend/src/services/fission/fission-guard.service.d.ts +21 -0
- package/dist/backend/backend/src/services/fission/fission-guard.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/fission/fission-guard.service.js +30 -0
- package/dist/backend/backend/src/services/fission/fission-guard.service.js.map +1 -1
- package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.d.ts +4 -0
- package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.js +8 -0
- package/dist/backend/backend/src/services/intent-task/intent-classifier.rules.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.d.ts +79 -58
- package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.js +140 -65
- package/dist/backend/backend/src/services/orchestrator/onboarding/materialize-team.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.d.ts +117 -0
- package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.js +189 -0
- package/dist/backend/backend/src/services/orchestrator/onboarding/synthesize-hierarchy.js.map +1 -0
- package/dist/backend/backend/src/services/orchestrator/onboarding-mode-loader.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/onboarding-mode-loader.js +1 -0
- package/dist/backend/backend/src/services/orchestrator/onboarding-mode-loader.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/onboarding-mode.skill-allowlist.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/onboarding-mode.skill-allowlist.js +2 -0
- package/dist/backend/backend/src/services/orchestrator/onboarding-mode.skill-allowlist.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/prompts/onboarding-mode.prompt.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/prompts/onboarding-mode.prompt.js +17 -1
- package/dist/backend/backend/src/services/orchestrator/prompts/onboarding-mode.prompt.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +50 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +71 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +18 -0
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js +75 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts +115 -0
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js +189 -3
- package/dist/backend/backend/src/services/session/pty/pty-session-backend.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.js +61 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/template/template.service.js +67 -2
- package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/cascade-request-status.d.ts +19 -1
- package/dist/backend/backend/src/services/v3/cascade-request-status.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/cascade-request-status.js +39 -2
- package/dist/backend/backend/src/services/v3/cascade-request-status.js.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts +41 -0
- package/dist/backend/backend/src/services/v3/escalation-router.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.js +169 -0
- package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/request-cascade.subscriber.d.ts +4 -1
- package/dist/backend/backend/src/services/v3/request-cascade.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request-cascade.subscriber.js +21 -0
- package/dist/backend/backend/src/services/v3/request-cascade.subscriber.js.map +1 -1
- package/dist/backend/backend/src/types/intent-task.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/intent-task.types.js +8 -0
- package/dist/backend/backend/src/types/intent-task.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/request.types.d.ts +1 -1
- package/dist/backend/backend/src/types/v2/request.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/request.types.js +1 -0
- package/dist/backend/backend/src/types/v2/request.types.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +12 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +12 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/package.json +9 -3
- package/packages/crewly-agent/README.md +27 -0
- package/packages/crewly-agent/bin/crewly-agent +33 -0
- package/packages/crewly-agent/package.json +39 -0
- package/packages/crewly-agent/src/cli.ts +168 -0
- package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +2355 -0
- package/packages/crewly-agent/src/runtime/agent-runner.service.ts +1827 -0
- package/packages/crewly-agent/src/runtime/agent-stream.service.test.ts +153 -0
- package/packages/crewly-agent/src/runtime/agent-stream.service.ts +225 -0
- package/packages/crewly-agent/src/runtime/agent-worker.test.ts +171 -0
- package/packages/crewly-agent/src/runtime/agent-worker.ts +193 -0
- package/packages/crewly-agent/src/runtime/api-client.ts +143 -0
- package/packages/crewly-agent/src/runtime/approval-queue.service.ts +307 -0
- package/packages/crewly-agent/src/runtime/audit-log.service.test.ts +208 -0
- package/packages/crewly-agent/src/runtime/audit-log.service.ts +332 -0
- package/packages/crewly-agent/src/runtime/audit-trail.service.test.ts +178 -0
- package/packages/crewly-agent/src/runtime/audit-trail.service.ts +151 -0
- package/packages/crewly-agent/src/runtime/auditor-tools.test.ts +274 -0
- package/packages/crewly-agent/src/runtime/auditor-tools.ts +311 -0
- package/packages/crewly-agent/src/runtime/cloud-config.ts +67 -0
- package/packages/crewly-agent/src/runtime/deepseek-sse-transform.test.ts +165 -0
- package/packages/crewly-agent/src/runtime/deepseek-sse-transform.ts +168 -0
- package/packages/crewly-agent/src/runtime/env-isolation.service.ts +246 -0
- package/packages/crewly-agent/src/runtime/in-process-log-buffer.test.ts +280 -0
- package/packages/crewly-agent/src/runtime/in-process-log-buffer.ts +317 -0
- package/packages/crewly-agent/src/runtime/index.ts +38 -0
- package/packages/crewly-agent/src/runtime/mcp-tool-bridge.test.ts +352 -0
- package/packages/crewly-agent/src/runtime/mcp-tool-bridge.ts +244 -0
- package/packages/crewly-agent/src/runtime/model-manager.test.ts +326 -0
- package/packages/crewly-agent/src/runtime/model-manager.ts +363 -0
- package/packages/crewly-agent/src/runtime/output-filter.service.ts +175 -0
- package/packages/crewly-agent/src/runtime/prompt-guard.service.ts +303 -0
- package/packages/crewly-agent/src/runtime/rate-limiter.test.ts +228 -0
- package/packages/crewly-agent/src/runtime/rate-limiter.ts +353 -0
- package/packages/crewly-agent/src/runtime/tool-registry.test.ts +2510 -0
- package/packages/crewly-agent/src/runtime/tool-registry.ts +2104 -0
- package/packages/crewly-agent/src/runtime/types.test.ts +519 -0
- package/packages/crewly-agent/src/runtime/types.ts +637 -0
- package/packages/crewly-agent/src/runtime/web-search.tool.test.ts +131 -0
- package/packages/crewly-agent/src/runtime/web-search.tool.ts +140 -0
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crewly Agent Runtime Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Types for the in-process Crewly Agent runtime powered by Vercel AI SDK.
|
|
5
|
+
* Covers model configuration, conversation state, and agent lifecycle.
|
|
6
|
+
*
|
|
7
|
+
* @module services/agent/crewly-agent/types
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ModelMessage } from 'ai';
|
|
11
|
+
import type { z } from 'zod';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Minimal MCP server configuration used by the standalone runtime.
|
|
15
|
+
* Mirrors OSS `McpServerConfig` from `services/mcp-client.ts` — kept
|
|
16
|
+
* field-compatible so the bridge can pass JSON payloads unchanged.
|
|
17
|
+
*/
|
|
18
|
+
export interface McpServerConfig {
|
|
19
|
+
command?: string;
|
|
20
|
+
args?: string[];
|
|
21
|
+
env?: Record<string, string>;
|
|
22
|
+
cwd?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** MCP tool metadata used by the standalone tool bridge. */
|
|
29
|
+
export interface McpToolInfo {
|
|
30
|
+
serverName: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
inputSchema: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** MCP call result shape. */
|
|
37
|
+
export interface McpCallResult {
|
|
38
|
+
isError: boolean;
|
|
39
|
+
content: Array<Record<string, unknown>>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Interface a caller can use to inject an MCP client implementation.
|
|
44
|
+
* Standalone has no concrete implementation by default; OSS bridges via
|
|
45
|
+
* the JSON protocol if MCP is needed in cloud-resident agents.
|
|
46
|
+
*/
|
|
47
|
+
export interface McpClientLike {
|
|
48
|
+
connectAll(serverConfigs: Record<string, McpServerConfig>): Promise<Map<string, Error>>;
|
|
49
|
+
listTools(): McpToolInfo[];
|
|
50
|
+
callTool(serverName: string, toolName: string, args: Record<string, unknown>): Promise<McpCallResult>;
|
|
51
|
+
disconnectAll(): Promise<void>;
|
|
52
|
+
getConnectedServers(): string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sensitivity overrides for MCP tools, keyed by 'serverName:toolName' or
|
|
57
|
+
* just 'toolName'. Mirrors the OSS `McpSensitivityOverrides` type formerly
|
|
58
|
+
* imported from `./mcp-tool-bridge.js`.
|
|
59
|
+
*/
|
|
60
|
+
export type McpSensitivityOverrides = Record<string, 'safe' | 'sensitive' | 'destructive'>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Supported model providers for Crewly Agent.
|
|
64
|
+
*
|
|
65
|
+
* Note: 'deepseek' is served through the OpenAI-compatible API at
|
|
66
|
+
* https://api.deepseek.com/v1, but is exposed as a distinct provider
|
|
67
|
+
* so users can select it explicitly. Its API key (DEEPSEEK_API_KEY) is
|
|
68
|
+
* read from the environment, not from the settings service — see
|
|
69
|
+
* model-manager.ts for details.
|
|
70
|
+
*/
|
|
71
|
+
export type ModelProvider = 'anthropic' | 'openai' | 'google' | 'ollama' | 'deepseek';
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* All valid model providers
|
|
75
|
+
*/
|
|
76
|
+
export const MODEL_PROVIDERS: readonly ModelProvider[] = [
|
|
77
|
+
'anthropic',
|
|
78
|
+
'openai',
|
|
79
|
+
'google',
|
|
80
|
+
'ollama',
|
|
81
|
+
'deepseek',
|
|
82
|
+
] as const;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Configuration for a specific model instance
|
|
86
|
+
*/
|
|
87
|
+
export interface ModelConfig {
|
|
88
|
+
/** Model provider (e.g., 'anthropic', 'openai', 'google', 'ollama') */
|
|
89
|
+
provider: ModelProvider;
|
|
90
|
+
/** Model identifier (e.g., 'claude-sonnet-4-20250514', 'gpt-4o', 'gemini-2.0-flash') */
|
|
91
|
+
modelId: string;
|
|
92
|
+
/** Optional temperature override (0-1) */
|
|
93
|
+
temperature?: number;
|
|
94
|
+
/** Optional max tokens per response */
|
|
95
|
+
maxTokens?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Conversation state maintained by the AgentRunner
|
|
100
|
+
*/
|
|
101
|
+
export interface ConversationState {
|
|
102
|
+
/** AI SDK ModelMessage array — the full conversation history */
|
|
103
|
+
messages: ModelMessage[];
|
|
104
|
+
/** System prompt loaded from prompt.md */
|
|
105
|
+
systemPrompt: string;
|
|
106
|
+
/** Cumulative token usage across all generateText calls */
|
|
107
|
+
totalTokens: { input: number; output: number };
|
|
108
|
+
/** Conversation creation time */
|
|
109
|
+
createdAt: Date;
|
|
110
|
+
/** Last activity timestamp */
|
|
111
|
+
lastActivityAt: Date;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Configuration for the Crewly Agent runtime
|
|
116
|
+
*/
|
|
117
|
+
export interface CrewlyAgentConfig {
|
|
118
|
+
/** Model configuration for the agent */
|
|
119
|
+
model: ModelConfig;
|
|
120
|
+
/** Maximum reasoning steps per generateText call */
|
|
121
|
+
maxSteps: number;
|
|
122
|
+
/** Session name for this agent instance */
|
|
123
|
+
sessionName: string;
|
|
124
|
+
/** Agent role (e.g. developer, product-manager) */
|
|
125
|
+
role?: string;
|
|
126
|
+
/** Base URL for the Crewly REST API */
|
|
127
|
+
apiBaseUrl: string;
|
|
128
|
+
/** System prompt content (loaded from prompt.md) */
|
|
129
|
+
systemPrompt: string;
|
|
130
|
+
/** Maximum conversation history messages before compaction triggers */
|
|
131
|
+
maxHistoryMessages: number;
|
|
132
|
+
/** Token budget threshold (percentage of context window) for compaction */
|
|
133
|
+
compactionThreshold: number;
|
|
134
|
+
/** Project path for memory and task tools (auto-injected) */
|
|
135
|
+
projectPath?: string;
|
|
136
|
+
/** Team member ID for heartbeat tracking (used as key in teamAgentStatus.json) */
|
|
137
|
+
memberId?: string;
|
|
138
|
+
/** MCP server configurations for external tool integration */
|
|
139
|
+
mcpServers?: Record<string, McpServerConfig>;
|
|
140
|
+
/** Sensitivity overrides for MCP tools (key: 'serverName:toolName' or 'toolName') */
|
|
141
|
+
mcpSensitivityOverrides?: McpSensitivityOverrides;
|
|
142
|
+
/**
|
|
143
|
+
* Eval mode flag. When true:
|
|
144
|
+
* - Strips TL delegation-first instructions from system prompt
|
|
145
|
+
* - Agent implements directly instead of delegating to workers
|
|
146
|
+
* - Enables post-execution deliverable self-check (Stop Hook)
|
|
147
|
+
*/
|
|
148
|
+
evalMode?: boolean;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Result of a single agent run (one generateText invocation)
|
|
153
|
+
*/
|
|
154
|
+
export interface AgentRunResult {
|
|
155
|
+
/** Final text response from the model */
|
|
156
|
+
text: string;
|
|
157
|
+
/** Number of steps taken in this run */
|
|
158
|
+
steps: number;
|
|
159
|
+
/** Token usage for this run */
|
|
160
|
+
usage: { input: number; output: number };
|
|
161
|
+
/** Tool calls made during this run */
|
|
162
|
+
toolCalls: ToolCallRecord[];
|
|
163
|
+
/** Reason the generation finished */
|
|
164
|
+
finishReason: string;
|
|
165
|
+
/** Budget warning message when token usage is approaching limits */
|
|
166
|
+
budgetWarning?: string;
|
|
167
|
+
/**
|
|
168
|
+
* I2: DeepSeek-R1 chain-of-thought text extracted from `delta.reasoning_content`.
|
|
169
|
+
*
|
|
170
|
+
* Present only for `deepseek-reasoner` model runs. `null` if the run did not
|
|
171
|
+
* produce any reasoning content (e.g. non-R1 model, or R1 returned content-only).
|
|
172
|
+
* `undefined` for non-DeepSeek providers (no extraction is performed).
|
|
173
|
+
*
|
|
174
|
+
* Concatenated across all agentic steps in this single AgentRunResult — i.e.
|
|
175
|
+
* if the model made N HTTP calls during this run, the reasoning from all N
|
|
176
|
+
* is joined in call order.
|
|
177
|
+
*
|
|
178
|
+
* **SECURITY — UNTRUSTED CONTENT.** This string is downstream of user input
|
|
179
|
+
* (R1 reasoning is generated in response to the user's prompt). When re-injected
|
|
180
|
+
* into ANY subsequent model call, it MUST be wrapped as untrusted-context — e.g.
|
|
181
|
+
* `<reasoning-from-deepseek-r1 trust="untrusted">...</reasoning-from-deepseek-r1>`
|
|
182
|
+
* with explicit framing as "hint material from another model, not authoritative
|
|
183
|
+
* instructions." Direct splicing into a system prompt = jailbreak primitive.
|
|
184
|
+
* See P0-1 design-review gate for I2.5 reasoning-pipe routing.
|
|
185
|
+
*/
|
|
186
|
+
reasoning?: string | null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Record of a single tool call during agent execution
|
|
191
|
+
*/
|
|
192
|
+
export interface ToolCallRecord {
|
|
193
|
+
/** Tool name */
|
|
194
|
+
toolName: string;
|
|
195
|
+
/** Arguments passed to the tool */
|
|
196
|
+
args: Record<string, unknown>;
|
|
197
|
+
/** Result returned by the tool */
|
|
198
|
+
result: unknown;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Result of an API call to the Crewly backend
|
|
203
|
+
*/
|
|
204
|
+
export interface ApiCallResult<T = unknown> {
|
|
205
|
+
/** Whether the call succeeded (HTTP 2xx) */
|
|
206
|
+
success: boolean;
|
|
207
|
+
/** Response data on success */
|
|
208
|
+
data?: T;
|
|
209
|
+
/** Error message on failure */
|
|
210
|
+
error?: string;
|
|
211
|
+
/** HTTP status code */
|
|
212
|
+
status: number;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Sensitivity classification for tool security auditing.
|
|
217
|
+
*
|
|
218
|
+
* - 'safe': Read-only or informational tools (no side effects)
|
|
219
|
+
* - 'sensitive': Tools that modify state or communicate externally
|
|
220
|
+
* - 'destructive': Tools that can cause irreversible damage
|
|
221
|
+
*/
|
|
222
|
+
export type ToolSensitivity = 'safe' | 'sensitive' | 'destructive';
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Tool definition shape matching AI SDK Tool interface.
|
|
226
|
+
* Shared by both the main tool registry and the auditor tool registry.
|
|
227
|
+
*/
|
|
228
|
+
export interface ToolDefinition {
|
|
229
|
+
/** Human-readable description of what the tool does */
|
|
230
|
+
description: string;
|
|
231
|
+
/** Zod schema for validating tool input */
|
|
232
|
+
inputSchema: z.ZodType;
|
|
233
|
+
/** Execute the tool with the given validated arguments */
|
|
234
|
+
execute: (args: Record<string, unknown>) => Promise<unknown>;
|
|
235
|
+
/** Security sensitivity classification for audit purposes */
|
|
236
|
+
sensitivity?: ToolSensitivity;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Callbacks for streaming events during agent execution.
|
|
241
|
+
* Emitted in real-time as the model generates text and calls tools.
|
|
242
|
+
*/
|
|
243
|
+
export interface StreamingEventCallbacks {
|
|
244
|
+
/** Called when a text chunk arrives from the model */
|
|
245
|
+
onTextChunk?: (chunk: string) => void;
|
|
246
|
+
/** Called when a tool call starts executing */
|
|
247
|
+
onToolCallStart?: (toolName: string, args: Record<string, unknown>) => void;
|
|
248
|
+
/** Called when a tool call completes */
|
|
249
|
+
onToolCallFinish?: (toolName: string, args: Record<string, unknown>, result: unknown, durationMs: number) => void;
|
|
250
|
+
/** Called when a reasoning step completes */
|
|
251
|
+
onStepFinish?: (stepIndex: number, hasToolCalls: boolean) => void;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A single entry in the security audit trail.
|
|
256
|
+
* Records every tool invocation with timing, classification, and result status.
|
|
257
|
+
*/
|
|
258
|
+
export interface AuditEntry {
|
|
259
|
+
/** ISO timestamp of the tool invocation */
|
|
260
|
+
timestamp: string;
|
|
261
|
+
/** Agent session name that invoked the tool */
|
|
262
|
+
sessionName?: string;
|
|
263
|
+
/** Name of the tool that was called */
|
|
264
|
+
toolName: string;
|
|
265
|
+
/** Sensitivity classification of the tool */
|
|
266
|
+
sensitivity: ToolSensitivity;
|
|
267
|
+
/** Arguments passed to the tool (sanitized — no secrets) */
|
|
268
|
+
args: Record<string, unknown>;
|
|
269
|
+
/** Whether the tool call succeeded */
|
|
270
|
+
success: boolean;
|
|
271
|
+
/** Error message if the call failed */
|
|
272
|
+
error?: string;
|
|
273
|
+
/** Execution duration in milliseconds */
|
|
274
|
+
durationMs: number;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Security policy configuration for the agent runtime.
|
|
279
|
+
* Controls which tool sensitivity levels require approval or are blocked.
|
|
280
|
+
*/
|
|
281
|
+
export interface SecurityPolicy {
|
|
282
|
+
/** Whether audit logging is enabled */
|
|
283
|
+
auditEnabled: boolean;
|
|
284
|
+
/** Tool sensitivity levels that require explicit approval */
|
|
285
|
+
requireApproval: ToolSensitivity[];
|
|
286
|
+
/** Tool names that are completely blocked */
|
|
287
|
+
blockedTools: string[];
|
|
288
|
+
/** Maximum audit log entries to retain in memory */
|
|
289
|
+
maxAuditEntries: number;
|
|
290
|
+
/**
|
|
291
|
+
* Read-only audit mode. When enabled, all tools classified as
|
|
292
|
+
* 'sensitive' or 'destructive' are blocked — only 'safe' (read-only)
|
|
293
|
+
* tools can execute. Tool invocations are still logged to the audit trail.
|
|
294
|
+
*/
|
|
295
|
+
readOnlyMode: boolean;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Tools that perform write/modify operations.
|
|
300
|
+
* Used by readOnlyMode to determine which tools to block.
|
|
301
|
+
*/
|
|
302
|
+
export const WRITE_TOOLS: readonly string[] = [
|
|
303
|
+
'edit_file',
|
|
304
|
+
'write_file',
|
|
305
|
+
'start_agent',
|
|
306
|
+
'stop_agent',
|
|
307
|
+
'handle_agent_failure',
|
|
308
|
+
'delegate_task',
|
|
309
|
+
'send_message',
|
|
310
|
+
'reply_slack',
|
|
311
|
+
'broadcast',
|
|
312
|
+
'schedule_check',
|
|
313
|
+
'cancel_schedule',
|
|
314
|
+
'register_self',
|
|
315
|
+
'report_status',
|
|
316
|
+
'remember',
|
|
317
|
+
'complete_task',
|
|
318
|
+
] as const;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Context budget status for the current conversation.
|
|
322
|
+
* Tracks token usage against the model's context window limit.
|
|
323
|
+
*/
|
|
324
|
+
export interface ContextBudgetStatus {
|
|
325
|
+
/** Total tokens used so far (input + output) */
|
|
326
|
+
totalTokensUsed: number;
|
|
327
|
+
/** Estimated context window size for the current model */
|
|
328
|
+
contextWindowSize: number;
|
|
329
|
+
/** Usage as a fraction (0.0 - 1.0) */
|
|
330
|
+
usagePercent: number;
|
|
331
|
+
/** Current threshold level based on compactionThreshold */
|
|
332
|
+
level: 'normal' | 'warning' | 'critical';
|
|
333
|
+
/** Number of messages in conversation history */
|
|
334
|
+
messageCount: number;
|
|
335
|
+
/** Whether auto-compaction will trigger on next message */
|
|
336
|
+
compactionPending: boolean;
|
|
337
|
+
/** Human-readable summary */
|
|
338
|
+
summary: string;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Estimated context window sizes (in tokens) for known models.
|
|
343
|
+
* Used for budget tracking when the model doesn't report its own limit.
|
|
344
|
+
*/
|
|
345
|
+
export const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
346
|
+
// Anthropic
|
|
347
|
+
'claude-opus-4-20250514': 200_000,
|
|
348
|
+
'claude-sonnet-4-20250514': 200_000,
|
|
349
|
+
'claude-haiku-4-20250506': 200_000,
|
|
350
|
+
// Google
|
|
351
|
+
'gemini-2.0-flash': 1_000_000,
|
|
352
|
+
'gemini-2.5-flash-preview-05-20': 1_000_000,
|
|
353
|
+
'gemini-3-flash-preview': 1_000_000,
|
|
354
|
+
// OpenAI
|
|
355
|
+
'gpt-4o': 128_000,
|
|
356
|
+
'gpt-4o-mini': 128_000,
|
|
357
|
+
'gpt-4-turbo': 128_000,
|
|
358
|
+
// DeepSeek — V3 chat and R1 reasoner both ship with a 64k context window.
|
|
359
|
+
// Without these entries the manager falls back to `default: 128_000`, which
|
|
360
|
+
// would let Crewly's compaction trigger (0.8 × budget) skip past the real
|
|
361
|
+
// 64k cap and let DeepSeek 4xx with context_length_exceeded.
|
|
362
|
+
'deepseek-chat': 64_000,
|
|
363
|
+
'deepseek-reasoner': 64_000,
|
|
364
|
+
// Fallback
|
|
365
|
+
default: 128_000,
|
|
366
|
+
} as const;
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Per-model floor on `maxOutputTokens` (the AI SDK's name for the
|
|
370
|
+
* `max_tokens` request param). Used to defend against a class of bugs where
|
|
371
|
+
* a low user-configured limit silently produces an empty response.
|
|
372
|
+
*
|
|
373
|
+
* Currently only `deepseek-reasoner` has a real floor: R1 mixes
|
|
374
|
+
* `reasoning_tokens` into the same `max_tokens` budget as `completion_tokens`,
|
|
375
|
+
* so a user value < ~200 can be entirely consumed by the reasoning trace,
|
|
376
|
+
* leaving `message.content = ""` (200 OK, no error).
|
|
377
|
+
*
|
|
378
|
+
* Models not listed here resolve to a floor of 0 (no clamp).
|
|
379
|
+
*
|
|
380
|
+
* Discovered 2026-05-03 via live smoke test D — see
|
|
381
|
+
* `.crewly/specs/2026-05-03-crewly-agent-deepseek-gap-list.md` (finding N5).
|
|
382
|
+
*/
|
|
383
|
+
export const MODEL_OUTPUT_TOKEN_FLOORS: Record<string, number> = {
|
|
384
|
+
'deepseek-reasoner': 1024,
|
|
385
|
+
} as const;
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Resolve the effective `maxOutputTokens` for a given model configuration,
|
|
389
|
+
* applying any per-model floor from `MODEL_OUTPUT_TOKEN_FLOORS`.
|
|
390
|
+
*
|
|
391
|
+
* If `config.maxTokens` is unset, falls back to the runtime default
|
|
392
|
+
* (`CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.maxTokens`). The result is always
|
|
393
|
+
* `>= floor` for the model, where `floor` defaults to 0.
|
|
394
|
+
*
|
|
395
|
+
* @param config - Model configuration (may have `maxTokens` undefined)
|
|
396
|
+
* @returns Effective max output tokens to pass to generateText/streamText
|
|
397
|
+
*/
|
|
398
|
+
export function resolveMaxOutputTokens(config: ModelConfig): number {
|
|
399
|
+
const requested = config.maxTokens ?? CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.maxTokens ?? 0;
|
|
400
|
+
const floor = MODEL_OUTPUT_TOKEN_FLOORS[config.modelId] ?? 0;
|
|
401
|
+
return Math.max(requested, floor);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Result of an autonomous context compaction operation.
|
|
406
|
+
* Returned by the compact_memory tool and requestCompaction().
|
|
407
|
+
*/
|
|
408
|
+
export interface CompactionResult {
|
|
409
|
+
/** Whether compaction was performed */
|
|
410
|
+
compacted: boolean;
|
|
411
|
+
/** Number of messages before compaction */
|
|
412
|
+
messagesBefore: number;
|
|
413
|
+
/** Number of messages after compaction */
|
|
414
|
+
messagesAfter: number;
|
|
415
|
+
/** Reason if compaction was skipped */
|
|
416
|
+
reason?: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Result of a tool approval check.
|
|
421
|
+
* Returned by the onCheckApproval callback to determine if a tool can execute.
|
|
422
|
+
*/
|
|
423
|
+
export interface ApprovalCheckResult {
|
|
424
|
+
/** Whether the tool is allowed to execute */
|
|
425
|
+
allowed: boolean;
|
|
426
|
+
/** Reason if the tool is blocked or requires approval */
|
|
427
|
+
reason?: string;
|
|
428
|
+
/** Whether the tool was blocked (vs requiring approval) */
|
|
429
|
+
blocked?: boolean;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Callbacks from tool registry to the agent runner.
|
|
434
|
+
* Allows tools to trigger runner-level operations like compaction and security checks.
|
|
435
|
+
*/
|
|
436
|
+
export interface ToolCallbacks {
|
|
437
|
+
/** Trigger intelligent context compaction */
|
|
438
|
+
onCompactMemory?: () => Promise<CompactionResult>;
|
|
439
|
+
/** Get current context budget status */
|
|
440
|
+
onGetContextBudget?: () => ContextBudgetStatus;
|
|
441
|
+
/** Record an audit entry for a tool call */
|
|
442
|
+
onAuditLog?: (entry: AuditEntry) => void;
|
|
443
|
+
/** Check if a tool is allowed to execute given current security policy */
|
|
444
|
+
onCheckApproval?: (toolName: string, sensitivity: ToolSensitivity) => ApprovalCheckResult;
|
|
445
|
+
/** Retrieve audit log entries with optional filters */
|
|
446
|
+
onGetAuditLog?: (filters: AuditLogFilters) => AuditEntry[];
|
|
447
|
+
/** Enqueue a tool call for approval when it requires explicit approval */
|
|
448
|
+
onEnqueueApproval?: (toolName: string, sensitivity: ToolSensitivity, args: Record<string, unknown>) => { approvalId: string };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Filters for querying the audit log.
|
|
453
|
+
*/
|
|
454
|
+
export interface AuditLogFilters {
|
|
455
|
+
/** Maximum entries to return (most recent first) */
|
|
456
|
+
limit: number;
|
|
457
|
+
/** Filter by sensitivity level */
|
|
458
|
+
sensitivity?: ToolSensitivity;
|
|
459
|
+
/** Filter by specific tool name */
|
|
460
|
+
toolName?: string;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Configuration for API key security guardrails.
|
|
465
|
+
* Controls output filtering, environment isolation, and prompt protection.
|
|
466
|
+
*/
|
|
467
|
+
export interface SecurityGuardrailConfig {
|
|
468
|
+
/** Enable output filtering (redact API keys from agent responses) */
|
|
469
|
+
outputFilterEnabled: boolean;
|
|
470
|
+
/** Enable environment isolation (strip secrets from bash child processes) */
|
|
471
|
+
envIsolationEnabled: boolean;
|
|
472
|
+
/** Enable prompt guard (block key extraction commands and prompt injections) */
|
|
473
|
+
promptGuardEnabled: boolean;
|
|
474
|
+
/** Additional env vars to explicitly allow in child processes */
|
|
475
|
+
explicitEnvVars: string[];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Default configuration values for Crewly Agent
|
|
480
|
+
*/
|
|
481
|
+
export const CREWLY_AGENT_DEFAULTS = {
|
|
482
|
+
/** Default max reasoning steps per generateText call (high to mimic unlimited like Claude Code) */
|
|
483
|
+
MAX_STEPS: 500,
|
|
484
|
+
/** Maximum tool calls allowed per single response to prevent polling dead-loops */
|
|
485
|
+
MAX_TOOL_CALLS_PER_RESPONSE: 15,
|
|
486
|
+
/** Consecutive identical tool calls before aborting (loop detection) */
|
|
487
|
+
LOOP_DETECTION_THRESHOLD: 3,
|
|
488
|
+
/** Consecutive error responses (404, 4xx, 5xx) from the same tool before aborting */
|
|
489
|
+
ERROR_LOOP_THRESHOLD: 3,
|
|
490
|
+
/** Default API base URL */
|
|
491
|
+
API_BASE_URL: 'http://localhost:8787',
|
|
492
|
+
/** Default max history messages before compaction */
|
|
493
|
+
MAX_HISTORY_MESSAGES: 100,
|
|
494
|
+
/** Default compaction threshold (80% of context window) */
|
|
495
|
+
COMPACTION_THRESHOLD: 0.8,
|
|
496
|
+
/** Default model configuration */
|
|
497
|
+
DEFAULT_MODEL: {
|
|
498
|
+
provider: 'google' as ModelProvider,
|
|
499
|
+
modelId: 'gemini-3-flash-preview',
|
|
500
|
+
temperature: 0.3,
|
|
501
|
+
maxTokens: 8192,
|
|
502
|
+
} satisfies ModelConfig,
|
|
503
|
+
/** HTTP request timeout in milliseconds */
|
|
504
|
+
API_TIMEOUT_MS: 30_000,
|
|
505
|
+
/** Heartbeat interval in milliseconds for keeping in-process agent active between messages */
|
|
506
|
+
HEARTBEAT_INTERVAL_MS: 30_000,
|
|
507
|
+
/** Maximum time in milliseconds for a single message processing (generateText call) — hard abort.
|
|
508
|
+
* Override via CREWLY_AGENT_MESSAGE_TIMEOUT_MS env var.
|
|
509
|
+
*
|
|
510
|
+
* This is the **default** timeout used when the model has no entry in
|
|
511
|
+
* `MODEL_TIMEOUT_MS`. Per-model overrides take precedence — see below. */
|
|
512
|
+
MESSAGE_TIMEOUT_MS: Number(process.env.CREWLY_AGENT_MESSAGE_TIMEOUT_MS) || 300_000,
|
|
513
|
+
/** I4 — Per-model hard-timeout overrides. Looked up by `modelId`.
|
|
514
|
+
*
|
|
515
|
+
* When the runtime service is about to run a message, it checks
|
|
516
|
+
* `MODEL_TIMEOUT_MS[config.model.modelId]` first; falls back to
|
|
517
|
+
* `MESSAGE_TIMEOUT_MS` if no entry exists.
|
|
518
|
+
*
|
|
519
|
+
* **Why per-model:** different models have radically different latency
|
|
520
|
+
* characteristics. DeepSeek-R1 (`deepseek-reasoner`) emits chain-of-thought
|
|
521
|
+
* reasoning_tokens which are slower to generate than plain content tokens.
|
|
522
|
+
* Live smoke (2026-05-03) measured R1 single-step at avg 2.8s/max 4.8s, but
|
|
523
|
+
* long-context (>32k) latency rises sharply per vendor docs, and multi-step
|
|
524
|
+
* agentic loops can accumulate 30+ steps. 5min default is tight; 10min gives
|
|
525
|
+
* safety margin without inviting runaway loops. See gap-list spec §I4 for
|
|
526
|
+
* measurement evidence. */
|
|
527
|
+
/**
|
|
528
|
+
* Frozen at module load via `Object.freeze` — defensive guard against
|
|
529
|
+
* runtime mutation. `as const` is compile-time only and the
|
|
530
|
+
* `Record<string, number>` cast widens it back to mutable, so freeze
|
|
531
|
+
* is the only line of defense against accidental writes like
|
|
532
|
+
* `CREWLY_AGENT_DEFAULTS.MODEL_TIMEOUT_MS['x'] = 999` from elsewhere
|
|
533
|
+
* in the codebase. Per-model overrides should be added by editing this
|
|
534
|
+
* file (treat as a config table, not runtime state).
|
|
535
|
+
*/
|
|
536
|
+
MODEL_TIMEOUT_MS: Object.freeze({
|
|
537
|
+
/** DeepSeek-R1: 2× default — reasoning chain-of-thought is slower per token. */
|
|
538
|
+
'deepseek-reasoner': 600_000,
|
|
539
|
+
}) as Readonly<Record<string, number>>,
|
|
540
|
+
/** Soft warning threshold in milliseconds — logs a warning but does not kill the request.
|
|
541
|
+
* Override via CREWLY_AGENT_MESSAGE_SOFT_WARNING_MS env var. */
|
|
542
|
+
MESSAGE_SOFT_WARNING_MS: Number(process.env.CREWLY_AGENT_MESSAGE_SOFT_WARNING_MS) || 240_000,
|
|
543
|
+
/** Cooldown period in milliseconds after rate limit retries are exhausted */
|
|
544
|
+
RATE_LIMIT_COOLDOWN_MS: 300_000,
|
|
545
|
+
/** Maximum number of automatic retries for recoverable errors (429, 5xx, network) */
|
|
546
|
+
MAX_RETRIES: 3,
|
|
547
|
+
/** Base delay in milliseconds for exponential backoff between retries */
|
|
548
|
+
RETRY_BASE_DELAY_MS: 1_000,
|
|
549
|
+
/** Default Ollama API base URL for local LLM provider */
|
|
550
|
+
OLLAMA_BASE_URL: 'http://localhost:11434/api',
|
|
551
|
+
/** Default security policy */
|
|
552
|
+
SECURITY_POLICY: {
|
|
553
|
+
auditEnabled: true,
|
|
554
|
+
requireApproval: [] as ToolSensitivity[],
|
|
555
|
+
blockedTools: [] as string[],
|
|
556
|
+
maxAuditEntries: 500,
|
|
557
|
+
readOnlyMode: false,
|
|
558
|
+
} satisfies SecurityPolicy,
|
|
559
|
+
/** Default security guardrail configuration */
|
|
560
|
+
SECURITY_GUARDRAILS: {
|
|
561
|
+
outputFilterEnabled: true,
|
|
562
|
+
envIsolationEnabled: true,
|
|
563
|
+
promptGuardEnabled: true,
|
|
564
|
+
explicitEnvVars: [],
|
|
565
|
+
} satisfies SecurityGuardrailConfig,
|
|
566
|
+
} as const;
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Type guard to check if a string is a valid ModelProvider
|
|
570
|
+
*
|
|
571
|
+
* @param value - String to check
|
|
572
|
+
* @returns True if the value is a valid ModelProvider
|
|
573
|
+
*/
|
|
574
|
+
export function isModelProvider(value: string): value is ModelProvider {
|
|
575
|
+
return MODEL_PROVIDERS.includes(value as ModelProvider);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Supported models for the UI dropdown.
|
|
580
|
+
* Format: provider/modelId → display label
|
|
581
|
+
*/
|
|
582
|
+
export const SUPPORTED_MODELS: Array<{ id: string; label: string; provider: ModelProvider }> = [
|
|
583
|
+
{ id: 'google/gemini-3-flash-preview', label: 'Gemini 3 Flash (Preview)', provider: 'google' },
|
|
584
|
+
{ id: 'google/gemini-2.5-flash-preview-05-20', label: 'Gemini 2.5 Flash', provider: 'google' },
|
|
585
|
+
{ id: 'anthropic/claude-sonnet-4-20250514', label: 'Claude Sonnet 4', provider: 'anthropic' },
|
|
586
|
+
{ id: 'anthropic/claude-haiku-4-20250514', label: 'Claude Haiku 4', provider: 'anthropic' },
|
|
587
|
+
{ id: 'openai/gpt-4o', label: 'GPT-4o', provider: 'openai' },
|
|
588
|
+
{ id: 'openai/gpt-4o-mini', label: 'GPT-4o Mini', provider: 'openai' },
|
|
589
|
+
{ id: 'deepseek/deepseek-chat', label: 'DeepSeek V3', provider: 'deepseek' },
|
|
590
|
+
{ id: 'deepseek/deepseek-reasoner', label: 'DeepSeek R1', provider: 'deepseek' },
|
|
591
|
+
{ id: 'ollama/llama3', label: 'Llama 3 (Ollama)', provider: 'ollama' },
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Parse a modelId string (provider/modelId) into a ModelConfig.
|
|
596
|
+
* Falls back to DEFAULT_MODEL if the input is invalid.
|
|
597
|
+
*
|
|
598
|
+
* @param modelId - Format: "provider/modelId" (e.g. "google/gemini-3-flash-preview")
|
|
599
|
+
* @returns Parsed ModelConfig
|
|
600
|
+
*/
|
|
601
|
+
export function parseModelId(modelId: string | undefined): ModelConfig {
|
|
602
|
+
if (!modelId || !modelId.includes('/')) {
|
|
603
|
+
return CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const slashIdx = modelId.indexOf('/');
|
|
607
|
+
const provider = modelId.substring(0, slashIdx);
|
|
608
|
+
const model = modelId.substring(slashIdx + 1);
|
|
609
|
+
|
|
610
|
+
if (!isModelProvider(provider) || !model) {
|
|
611
|
+
return CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return {
|
|
615
|
+
provider,
|
|
616
|
+
modelId: model,
|
|
617
|
+
temperature: CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.temperature,
|
|
618
|
+
maxTokens: CREWLY_AGENT_DEFAULTS.DEFAULT_MODEL.maxTokens,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Type guard to check if an object is a valid ModelConfig
|
|
624
|
+
*
|
|
625
|
+
* @param obj - Object to validate
|
|
626
|
+
* @returns True if the object has all required ModelConfig fields
|
|
627
|
+
*/
|
|
628
|
+
export function isModelConfig(obj: unknown): obj is ModelConfig {
|
|
629
|
+
if (typeof obj !== 'object' || obj === null) return false;
|
|
630
|
+
const candidate = obj as Record<string, unknown>;
|
|
631
|
+
return (
|
|
632
|
+
typeof candidate.provider === 'string' &&
|
|
633
|
+
isModelProvider(candidate.provider) &&
|
|
634
|
+
typeof candidate.modelId === 'string' &&
|
|
635
|
+
candidate.modelId.length > 0
|
|
636
|
+
);
|
|
637
|
+
}
|