@cjhyy/code-shell-core 0.6.0-rc.9 → 0.7.0
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/THIRD_PARTY_NOTICES.md +206 -0
- package/dist/automation/scheduler.d.ts +13 -7
- package/dist/automation/scheduler.js +116 -37
- package/dist/capability-control/service.d.ts +2 -0
- package/dist/capability-control/service.js +4 -2
- package/dist/cc-orchestrator/agent-adapter.d.ts +4 -0
- package/dist/cc-orchestrator/agent-adapter.js +11 -1
- package/dist/cc-orchestrator/codex-session-history.d.ts +14 -1
- package/dist/cc-orchestrator/codex-session-history.js +65 -5
- package/dist/cc-orchestrator/cwd-normalize.d.ts +2 -0
- package/dist/cc-orchestrator/cwd-normalize.js +19 -0
- package/dist/cc-orchestrator/external-agent-bindings.d.ts +27 -0
- package/dist/cc-orchestrator/external-agent-bindings.js +150 -0
- package/dist/cc-orchestrator/external-agent-changes.js +22 -5
- package/dist/cc-orchestrator/external-agent-driver.d.ts +1 -0
- package/dist/cc-orchestrator/external-agent-driver.js +265 -50
- package/dist/cc-orchestrator/external-agent-session-store.d.ts +23 -0
- package/dist/cc-orchestrator/external-agent-session-store.js +146 -0
- package/dist/cc-orchestrator/session-history.d.ts +35 -0
- package/dist/cc-orchestrator/session-history.js +98 -15
- package/dist/cli/agent-server-stdio.js +9 -2
- package/dist/context/compaction.d.ts +8 -1
- package/dist/context/compaction.js +49 -4
- package/dist/context/manager.d.ts +16 -2
- package/dist/context/manager.js +103 -19
- package/dist/credentials/access.d.ts +57 -0
- package/dist/credentials/access.js +185 -0
- package/dist/credentials/index.d.ts +3 -1
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/inject-credential-tool.d.ts +3 -1
- package/dist/credentials/inject-credential-tool.js +30 -11
- package/dist/credentials/oauth.d.ts +20 -0
- package/dist/credentials/oauth.js +114 -0
- package/dist/credentials/store.d.ts +1 -0
- package/dist/credentials/store.js +3 -1
- package/dist/credentials/types.d.ts +49 -3
- package/dist/credentials/use-credential-tool.d.ts +9 -1
- package/dist/credentials/use-credential-tool.js +58 -45
- package/dist/engine/engine.d.ts +23 -3
- package/dist/engine/engine.js +380 -255
- package/dist/engine/goal.d.ts +17 -0
- package/dist/engine/goal.js +16 -6
- package/dist/engine/image-policy.d.ts +6 -0
- package/dist/engine/image-policy.js +17 -6
- package/dist/engine/input-attachments.d.ts +13 -0
- package/dist/engine/input-attachments.js +398 -0
- package/dist/engine/model-facade.d.ts +5 -2
- package/dist/engine/model-facade.js +4 -4
- package/dist/engine/parse-task.d.ts +10 -0
- package/dist/engine/parse-task.js +5 -0
- package/dist/engine/run-image-input.d.ts +22 -0
- package/dist/engine/run-image-input.js +195 -0
- package/dist/engine/steer-queue.d.ts +3 -1
- package/dist/engine/steer-queue.js +10 -2
- package/dist/engine/streaming-tool-queue.d.ts +11 -7
- package/dist/engine/streaming-tool-queue.js +11 -7
- package/dist/engine/turn-loop.d.ts +37 -2
- package/dist/engine/turn-loop.js +228 -43
- package/dist/engine/types.d.ts +8 -0
- package/dist/git/worktree/crud.d.ts +69 -0
- package/dist/git/worktree/crud.js +206 -0
- package/dist/git/worktree/diff.d.ts +14 -0
- package/dist/git/worktree/diff.js +82 -0
- package/dist/git/worktree/git-exec.d.ts +7 -0
- package/dist/git/worktree/git-exec.js +51 -0
- package/dist/git/worktree/index.d.ts +5 -0
- package/dist/git/worktree/index.js +5 -0
- package/dist/git/worktree/query.d.ts +42 -0
- package/dist/git/worktree/query.js +121 -0
- package/dist/git/worktree/slug.d.ts +11 -0
- package/dist/git/worktree/slug.js +58 -0
- package/dist/git/worktree.d.ts +1 -84
- package/dist/git/worktree.js +5 -230
- package/dist/hooks/goal-stop-hook.d.ts +33 -1
- package/dist/hooks/goal-stop-hook.js +202 -34
- package/dist/index.d.ts +31 -28
- package/dist/index.js +27 -25
- package/dist/logging/logger.js +6 -6
- package/dist/logging/sanitize-messages.d.ts +10 -2
- package/dist/logging/sanitize-messages.js +21 -6
- package/dist/plugins/installer/checkUpdate.d.ts +4 -1
- package/dist/plugins/installer/checkUpdate.js +4 -2
- package/dist/plugins/installer/install.js +2 -0
- package/dist/plugins/installer/installFromSource.js +9 -1
- package/dist/plugins/installer/parseSource.d.ts +4 -1
- package/dist/plugins/installer/parseSource.js +28 -10
- package/dist/plugins/installer/sourcePath.d.ts +9 -0
- package/dist/plugins/installer/sourcePath.js +50 -0
- package/dist/plugins/installer/update.d.ts +4 -1
- package/dist/plugins/installer/update.js +5 -3
- package/dist/plugins/parseMarketplaceInput.d.ts +4 -1
- package/dist/plugins/parseMarketplaceInput.js +4 -3
- package/dist/plugins/pluginInstaller.js +24 -22
- package/dist/preset/index.d.ts +1 -0
- package/dist/preset/index.js +30 -13
- package/dist/prompt/sections/base.md +1 -1
- package/dist/protocol/chat-session-manager.d.ts +2 -0
- package/dist/protocol/chat-session-manager.js +38 -2
- package/dist/protocol/chat-session.d.ts +3 -0
- package/dist/protocol/chat-session.js +1 -0
- package/dist/protocol/client.d.ts +9 -4
- package/dist/protocol/client.js +18 -1
- package/dist/protocol/server.d.ts +30 -0
- package/dist/protocol/server.js +247 -47
- package/dist/protocol/types.d.ts +49 -0
- package/dist/protocol/types.js +6 -0
- package/dist/run/FileRunStore.js +10 -1
- package/dist/run/Heartbeat.js +12 -0
- package/dist/run/RunApprovalBackend.d.ts +3 -0
- package/dist/run/RunApprovalBackend.js +41 -6
- package/dist/run/RunLock.js +2 -0
- package/dist/run/RunManager.d.ts +2 -0
- package/dist/run/RunManager.js +64 -24
- package/dist/run/ids.d.ts +2 -0
- package/dist/run/ids.js +23 -0
- package/dist/runtime/background-shell.d.ts +1 -0
- package/dist/runtime/background-shell.js +23 -13
- package/dist/runtime/spawn-common.js +10 -0
- package/dist/services/auto-dream.d.ts +15 -4
- package/dist/services/auto-dream.js +20 -20
- package/dist/services/dream-consolidation.d.ts +2 -3
- package/dist/services/dream-consolidation.js +65 -15
- package/dist/services/extract-memories.d.ts +14 -5
- package/dist/services/extract-memories.js +20 -3
- package/dist/services/global-dream-promotion.d.ts +23 -0
- package/dist/services/global-dream-promotion.js +112 -0
- package/dist/services/memory-orchestrator.js +347 -34
- package/dist/session/memory.d.ts +61 -18
- package/dist/session/memory.js +342 -79
- package/dist/session/session-manager.d.ts +35 -1
- package/dist/session/session-manager.js +224 -4
- package/dist/session/transcript.d.ts +1 -1
- package/dist/session/transcript.js +17 -5
- package/dist/settings/manager.d.ts +1 -0
- package/dist/settings/manager.js +87 -37
- package/dist/settings/schema-export.d.ts +2 -3
- package/dist/settings/schema-export.js +2 -3
- package/dist/settings/schema.d.ts +21 -0
- package/dist/settings/schema.js +12 -0
- package/dist/skills/scanner.d.ts +3 -2
- package/dist/skills/scanner.js +12 -9
- package/dist/tool-system/builtin/agent-notifications.d.ts +11 -4
- package/dist/tool-system/builtin/agent-notifications.js +19 -7
- package/dist/tool-system/builtin/background-jobs.d.ts +35 -6
- package/dist/tool-system/builtin/background-jobs.js +116 -7
- package/dist/tool-system/builtin/background-work.d.ts +17 -18
- package/dist/tool-system/builtin/background-work.js +51 -5
- package/dist/tool-system/builtin/config.d.ts +2 -1
- package/dist/tool-system/builtin/config.js +16 -11
- package/dist/tool-system/builtin/cron-list.definition.d.ts +3 -0
- package/dist/tool-system/builtin/cron-list.definition.js +6 -0
- package/dist/tool-system/builtin/cron.d.ts +1 -2
- package/dist/tool-system/builtin/cron.js +9 -7
- package/dist/tool-system/builtin/drive-claude-code.d.ts +23 -2
- package/dist/tool-system/builtin/drive-claude-code.js +589 -48
- package/dist/tool-system/builtin/edit.js +5 -2
- package/dist/tool-system/builtin/generate-video.d.ts +1 -0
- package/dist/tool-system/builtin/generate-video.js +13 -4
- package/dist/tool-system/builtin/index.d.ts +8 -3
- package/dist/tool-system/builtin/index.js +50 -22
- package/dist/tool-system/builtin/lsp.d.ts +2 -1
- package/dist/tool-system/builtin/lsp.js +6 -3
- package/dist/tool-system/builtin/memory.js +40 -8
- package/dist/tool-system/builtin/notebook-edit.js +5 -2
- package/dist/tool-system/builtin/powershell.d.ts +5 -2
- package/dist/tool-system/builtin/powershell.js +11 -7
- package/dist/tool-system/builtin/read.js +118 -6
- package/dist/tool-system/builtin/sleep.d.ts +1 -2
- package/dist/tool-system/builtin/sleep.definition.d.ts +8 -0
- package/dist/tool-system/builtin/sleep.definition.js +28 -0
- package/dist/tool-system/builtin/sleep.js +1 -22
- package/dist/tool-system/builtin/view-image.d.ts +2 -2
- package/dist/tool-system/builtin/view-image.js +109 -19
- package/dist/tool-system/builtin/worktree.d.ts +4 -4
- package/dist/tool-system/builtin/worktree.js +297 -74
- package/dist/tool-system/builtin/write.js +5 -3
- package/dist/tool-system/context.d.ts +41 -1
- package/dist/tool-system/executor.d.ts +1 -5
- package/dist/tool-system/executor.js +94 -115
- package/dist/tool-system/mcp-manager.d.ts +31 -6
- package/dist/tool-system/mcp-manager.js +193 -78
- package/dist/tool-system/path-policy.d.ts +2 -0
- package/dist/tool-system/path-policy.js +41 -12
- package/dist/tool-system/permission.d.ts +28 -7
- package/dist/tool-system/permission.js +130 -49
- package/dist/tool-system/registry.js +11 -4
- package/dist/tool-system/tool-result-redaction.d.ts +7 -0
- package/dist/tool-system/tool-result-redaction.js +48 -0
- package/dist/tool-system/workspace-bridge.d.ts +11 -0
- package/dist/tool-system/workspace-bridge.js +1 -0
- package/dist/types.d.ts +78 -11
- package/dist/utils/toolDisplay.js +1 -1
- package/package.json +4 -3
package/dist/engine/goal.d.ts
CHANGED
|
@@ -47,6 +47,21 @@ export interface GoalConfig {
|
|
|
47
47
|
*/
|
|
48
48
|
setAtMs?: number;
|
|
49
49
|
}
|
|
50
|
+
/** A forced terminal outcome for one concrete persisted goal instance. */
|
|
51
|
+
export type GoalTerminationReason = "stop_blocks_exhausted" | "token_budget_exhausted" | "time_budget_exhausted" | "max_turns_exhausted";
|
|
52
|
+
/**
|
|
53
|
+
* Persisted terminal marker used to prevent a stale whole-state writer from
|
|
54
|
+
* making an exhausted goal armable again. `objective + setAtMs` is the goal
|
|
55
|
+
* instance identity; `terminatedAtMs` only orders competing tombstones.
|
|
56
|
+
*/
|
|
57
|
+
export interface GoalTerminal {
|
|
58
|
+
objective: string;
|
|
59
|
+
setAtMs?: number;
|
|
60
|
+
reason: GoalTerminationReason;
|
|
61
|
+
terminatedAtMs?: number;
|
|
62
|
+
}
|
|
63
|
+
/** True only when both values identify the same concrete goal instance. */
|
|
64
|
+
export declare function isSameGoalInstance(left: Pick<GoalConfig, "objective" | "setAtMs"> | undefined, right: Pick<GoalConfig, "objective" | "setAtMs"> | undefined): boolean;
|
|
50
65
|
/**
|
|
51
66
|
* Default consecutive-stop-block cap for goal runs. The real safety net for an
|
|
52
67
|
* unattended goal is the token/time budget + maxTurns; this cap only stops a
|
|
@@ -185,3 +200,5 @@ export declare function recordGoalUsage(tracker: GoalBudgetTracker, turnTokens:
|
|
|
185
200
|
* pass the current clock (and tests pass a fixed value).
|
|
186
201
|
*/
|
|
187
202
|
export declare function goalBudgetExceeded(tracker: GoalBudgetTracker, nowMs: number): boolean;
|
|
203
|
+
/** Identify which goal budget forced termination, preserving token-first precedence. */
|
|
204
|
+
export declare function goalBudgetTerminationReason(tracker: GoalBudgetTracker, nowMs: number): Extract<GoalTerminationReason, "token_budget_exhausted" | "time_budget_exhausted"> | undefined;
|
package/dist/engine/goal.js
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* it accumulates total tokens across turns and stamps a wall-clock start,
|
|
11
11
|
* so an unattended goal run can't burn tokens or wall time without bound.
|
|
12
12
|
*/
|
|
13
|
+
/** True only when both values identify the same concrete goal instance. */
|
|
14
|
+
export function isSameGoalInstance(left, right) {
|
|
15
|
+
return !!left && !!right && left.objective === right.objective && left.setAtMs === right.setAtMs;
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Default consecutive-stop-block cap for goal runs. The real safety net for an
|
|
15
19
|
* unattended goal is the token/time budget + maxTurns; this cap only stops a
|
|
@@ -94,7 +98,7 @@ export function normalizeGoal(raw) {
|
|
|
94
98
|
*/
|
|
95
99
|
export function resolveGoalSetAt(explicitObjective, stored, nowMs) {
|
|
96
100
|
const sameGoalContinues = !!stored && stored.objective === explicitObjective;
|
|
97
|
-
return sameGoalContinues ? stored.setAtMs ?? nowMs : nowMs;
|
|
101
|
+
return sameGoalContinues ? (stored.setAtMs ?? nowMs) : nowMs;
|
|
98
102
|
}
|
|
99
103
|
/** Interactive (no-goal) turn ceiling — a single prompt rarely needs more. */
|
|
100
104
|
export const INTERACTIVE_DEFAULT_MAX_TURNS = 100;
|
|
@@ -187,10 +191,16 @@ export function recordGoalUsage(tracker, turnTokens) {
|
|
|
187
191
|
* pass the current clock (and tests pass a fixed value).
|
|
188
192
|
*/
|
|
189
193
|
export function goalBudgetExceeded(tracker, nowMs) {
|
|
194
|
+
return goalBudgetTerminationReason(tracker, nowMs) !== undefined;
|
|
195
|
+
}
|
|
196
|
+
/** Identify which goal budget forced termination, preserving token-first precedence. */
|
|
197
|
+
export function goalBudgetTerminationReason(tracker, nowMs) {
|
|
190
198
|
const { tokenBudget, timeBudgetMs } = tracker.goal;
|
|
191
|
-
if (typeof tokenBudget === "number" && tracker.tokensUsed > tokenBudget)
|
|
192
|
-
return
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
if (typeof tokenBudget === "number" && tracker.tokensUsed > tokenBudget) {
|
|
200
|
+
return "token_budget_exhausted";
|
|
201
|
+
}
|
|
202
|
+
if (typeof timeBudgetMs === "number" && nowMs - tracker.startedAtMs > timeBudgetMs) {
|
|
203
|
+
return "time_budget_exhausted";
|
|
204
|
+
}
|
|
205
|
+
return undefined;
|
|
196
206
|
}
|
|
@@ -149,6 +149,12 @@ export interface DropOversizedResult {
|
|
|
149
149
|
droppedCount: number;
|
|
150
150
|
}
|
|
151
151
|
export declare function dropOversizedImages(images: readonly ParsedImage[]): DropOversizedResult;
|
|
152
|
+
export interface ImagePolicyByteInput {
|
|
153
|
+
name: string;
|
|
154
|
+
mime: string;
|
|
155
|
+
bytes: number;
|
|
156
|
+
}
|
|
157
|
+
export declare function enforceImageBytePolicy(images: readonly ImagePolicyByteInput[]): ImagePolicyVerdict;
|
|
152
158
|
export declare function enforceImagePolicy(images: readonly ParsedImage[]): ImagePolicyVerdict;
|
|
153
159
|
/**
|
|
154
160
|
* Collect the workspace file paths of any attached images that came from a
|
|
@@ -107,15 +107,13 @@ export function dropOversizedImages(images) {
|
|
|
107
107
|
if (dropped.length === 0) {
|
|
108
108
|
return { kept, placeholder: "", droppedCount: 0 };
|
|
109
109
|
}
|
|
110
|
-
const list = dropped
|
|
111
|
-
.map((d) => `「${d.name}」(~${fmtMB(d.bytes)})`)
|
|
112
|
-
.join("、");
|
|
110
|
+
const list = dropped.map((d) => `「${d.name}」(~${fmtMB(d.bytes)})`).join("、");
|
|
113
111
|
const placeholder = `[已自动跳过 ${dropped.length} 张超大图片:${list};` +
|
|
114
112
|
`单图上限 ${fmtMB(IMAGE_LIMITS.maxBytesPerImage)}。` +
|
|
115
113
|
`图片未进入对话历史,本轮其余内容继续。]`;
|
|
116
114
|
return { kept, placeholder, droppedCount: dropped.length };
|
|
117
115
|
}
|
|
118
|
-
export function
|
|
116
|
+
export function enforceImageBytePolicy(images) {
|
|
119
117
|
if (images.length === 0)
|
|
120
118
|
return { ok: true };
|
|
121
119
|
if (images.length > IMAGE_LIMITS.maxImagesPerTurn) {
|
|
@@ -126,13 +124,13 @@ export function enforceImagePolicy(images) {
|
|
|
126
124
|
`本次有 ${images.length} 张。请删掉一些再发。`,
|
|
127
125
|
totals: {
|
|
128
126
|
imageCount: images.length,
|
|
129
|
-
totalBytes: images.reduce((s, i) => s +
|
|
127
|
+
totalBytes: images.reduce((s, i) => s + i.bytes, 0),
|
|
130
128
|
},
|
|
131
129
|
};
|
|
132
130
|
}
|
|
133
131
|
let totalBytes = 0;
|
|
134
132
|
for (const img of images) {
|
|
135
|
-
const bytes =
|
|
133
|
+
const bytes = img.bytes;
|
|
136
134
|
if (bytes > IMAGE_LIMITS.maxBytesPerImage) {
|
|
137
135
|
return {
|
|
138
136
|
ok: false,
|
|
@@ -163,6 +161,13 @@ export function enforceImagePolicy(images) {
|
|
|
163
161
|
}
|
|
164
162
|
return { ok: true };
|
|
165
163
|
}
|
|
164
|
+
export function enforceImagePolicy(images) {
|
|
165
|
+
return enforceImageBytePolicy(images.map((img) => ({
|
|
166
|
+
name: img.name,
|
|
167
|
+
mime: img.mime,
|
|
168
|
+
bytes: byteLengthFromBase64(img.base64),
|
|
169
|
+
})));
|
|
170
|
+
}
|
|
166
171
|
/**
|
|
167
172
|
* Collect the workspace file paths of any attached images that came from a
|
|
168
173
|
* real file (the desktop composer's path-attach flow sets ParsedImage.name to
|
|
@@ -178,6 +183,12 @@ export function enforceImagePolicy(images) {
|
|
|
178
183
|
export function collectAttachedImagePaths(images, resolve, exists) {
|
|
179
184
|
const out = [];
|
|
180
185
|
for (const img of images) {
|
|
186
|
+
const path = img.path?.trim();
|
|
187
|
+
if (path) {
|
|
188
|
+
if (exists(resolve(path)))
|
|
189
|
+
out.push(path);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
181
192
|
const name = img.name?.trim();
|
|
182
193
|
if (!name)
|
|
183
194
|
continue;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { InputAttachmentMeta } from "../protocol/types.js";
|
|
2
|
+
import type { ParsedImage } from "./parse-task.js";
|
|
3
|
+
export interface InputAttachmentContext {
|
|
4
|
+
text: string;
|
|
5
|
+
images: ParsedImage[];
|
|
6
|
+
errors: string[];
|
|
7
|
+
hasStructuredImageAttachments: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface BuildInputAttachmentContextOptions {
|
|
10
|
+
includeImageBytes?: boolean;
|
|
11
|
+
expectedSessionId?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function buildInputAttachmentContext(attachments: readonly InputAttachmentMeta[] | undefined, cwd: string, options?: BuildInputAttachmentContextOptions): Promise<InputAttachmentContext>;
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { logger } from "../logging/logger.js";
|
|
5
|
+
import { classifyPath } from "../tool-system/path-policy.js";
|
|
6
|
+
import { enforceImageBytePolicy } from "./image-policy.js";
|
|
7
|
+
const IMAGE_MIME_BY_EXT = {
|
|
8
|
+
".png": "image/png",
|
|
9
|
+
".jpg": "image/jpeg",
|
|
10
|
+
".jpeg": "image/jpeg",
|
|
11
|
+
".gif": "image/gif",
|
|
12
|
+
".webp": "image/webp",
|
|
13
|
+
};
|
|
14
|
+
const MAX_DIRECTORY_TREE_ENTRIES = 200;
|
|
15
|
+
const MAX_DIRECTORY_TREE_DEPTH = 2;
|
|
16
|
+
export async function buildInputAttachmentContext(attachments, cwd, options = {}) {
|
|
17
|
+
if (!attachments || attachments.length === 0) {
|
|
18
|
+
return { text: "", images: [], errors: [], hasStructuredImageAttachments: false };
|
|
19
|
+
}
|
|
20
|
+
const cwdReal = await realpath(cwd);
|
|
21
|
+
const includeImageBytes = options.includeImageBytes ?? true;
|
|
22
|
+
const expectedSessionId = options.expectedSessionId;
|
|
23
|
+
const textBlocks = [];
|
|
24
|
+
const images = [];
|
|
25
|
+
const errors = [];
|
|
26
|
+
const pendingImages = [];
|
|
27
|
+
const diagnostics = [];
|
|
28
|
+
let hasStructuredImageAttachments = false;
|
|
29
|
+
logger.info("engine.run.input_attachments.start", {
|
|
30
|
+
attachmentCount: attachments.length,
|
|
31
|
+
expectedSessionId: expectedSessionId ?? null,
|
|
32
|
+
includeImageBytes,
|
|
33
|
+
});
|
|
34
|
+
if (!expectedSessionId) {
|
|
35
|
+
logger.warn("engine.run.input_attachments.invalid_session", {
|
|
36
|
+
attachmentCount: attachments.length,
|
|
37
|
+
reason: "missing_expected_session_id",
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
text: "",
|
|
41
|
+
images: [],
|
|
42
|
+
errors: ["input attachments require an expected sessionId"],
|
|
43
|
+
hasStructuredImageAttachments: false,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (!isSafeSessionPathSegment(expectedSessionId)) {
|
|
47
|
+
logger.warn("engine.run.input_attachments.invalid_session", {
|
|
48
|
+
attachmentCount: attachments.length,
|
|
49
|
+
reason: "unsafe_expected_session_id",
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
text: "",
|
|
53
|
+
images: [],
|
|
54
|
+
errors: [`expected sessionId "${expectedSessionId}" is not a safe path segment`],
|
|
55
|
+
hasStructuredImageAttachments: false,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
for (const [index, attachment] of attachments.entries()) {
|
|
59
|
+
if (!attachment || typeof attachment !== "object") {
|
|
60
|
+
errors.push(`attachment at index ${index} is not an object`);
|
|
61
|
+
diagnostics.push({
|
|
62
|
+
index,
|
|
63
|
+
id: "(unknown)",
|
|
64
|
+
kind: typeof attachment,
|
|
65
|
+
path: null,
|
|
66
|
+
mime: null,
|
|
67
|
+
stage: "invalid_metadata",
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const id = stringField(attachment.id) ?? "(unknown)";
|
|
72
|
+
const kind = stringField(attachment.kind) ?? "(unknown)";
|
|
73
|
+
const declaredMime = stringField(attachment.mime);
|
|
74
|
+
const displayPath = firstString(attachment.path, attachment.relPath, attachment.absPath);
|
|
75
|
+
if (!displayPath) {
|
|
76
|
+
errors.push(`attachment ${id} has no valid path`);
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
index,
|
|
79
|
+
id,
|
|
80
|
+
kind,
|
|
81
|
+
path: null,
|
|
82
|
+
mime: declaredMime ?? null,
|
|
83
|
+
stage: "invalid_path_metadata",
|
|
84
|
+
});
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (attachment.sessionId !== expectedSessionId) {
|
|
88
|
+
errors.push(`attachment ${id === "(unknown)" ? displayPath : id} session mismatch: expected ${expectedSessionId}, got ${attachment.sessionId}`);
|
|
89
|
+
diagnostics.push({
|
|
90
|
+
index,
|
|
91
|
+
id,
|
|
92
|
+
kind,
|
|
93
|
+
path: displayPath,
|
|
94
|
+
mime: declaredMime ?? null,
|
|
95
|
+
stage: "session_mismatch",
|
|
96
|
+
});
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const resolved = resolveAttachmentPath(attachment, cwd, displayPath);
|
|
100
|
+
let info;
|
|
101
|
+
try {
|
|
102
|
+
info = await stat(resolved);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
errors.push(`attachment ${id === "(unknown)" ? displayPath : id} stat failed: ${err.message}`);
|
|
106
|
+
diagnostics.push({
|
|
107
|
+
index,
|
|
108
|
+
id,
|
|
109
|
+
kind,
|
|
110
|
+
path: displayPath,
|
|
111
|
+
mime: declaredMime ?? null,
|
|
112
|
+
stage: "stat_failed",
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const policy = classifyPath(resolved, { workspaceRoot: cwdReal, operation: "read" });
|
|
117
|
+
if (policy.decision !== "allow") {
|
|
118
|
+
errors.push(`attachment ${id === "(unknown)" ? displayPath : id} blocked by path policy: ${policy.reason}`);
|
|
119
|
+
diagnostics.push({
|
|
120
|
+
index,
|
|
121
|
+
id,
|
|
122
|
+
kind,
|
|
123
|
+
path: displayPath,
|
|
124
|
+
mime: declaredMime ?? null,
|
|
125
|
+
stage: "path_policy_blocked",
|
|
126
|
+
});
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const realPath = policy.resolvedPath;
|
|
130
|
+
const stagedPathError = await validateStagedAttachmentPath(attachment, realPath, cwdReal, expectedSessionId, displayPath);
|
|
131
|
+
if (stagedPathError) {
|
|
132
|
+
errors.push(stagedPathError);
|
|
133
|
+
diagnostics.push({
|
|
134
|
+
index,
|
|
135
|
+
id,
|
|
136
|
+
kind,
|
|
137
|
+
path: displayPath,
|
|
138
|
+
mime: declaredMime ?? null,
|
|
139
|
+
stage: "staged_path_blocked",
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (attachment.kind === "directory" || info.isDirectory()) {
|
|
144
|
+
const tree = await directoryTree(realPath, cwdReal).catch((err) => ({
|
|
145
|
+
lines: [`(directory tree unavailable: ${err.message})`],
|
|
146
|
+
truncated: true,
|
|
147
|
+
entryCount: 0,
|
|
148
|
+
}));
|
|
149
|
+
textBlocks.push([
|
|
150
|
+
`<attached-directory path="${escapeText(displayPath)}" entries="${tree.entryCount}" truncated="${tree.truncated}">`,
|
|
151
|
+
...tree.lines,
|
|
152
|
+
`</attached-directory>`,
|
|
153
|
+
].join("\n"));
|
|
154
|
+
diagnostics.push({
|
|
155
|
+
index,
|
|
156
|
+
id,
|
|
157
|
+
kind: "directory",
|
|
158
|
+
path: displayPath,
|
|
159
|
+
mime: declaredMime ?? null,
|
|
160
|
+
stage: tree.truncated ? "directory_metadata_truncated" : "directory_metadata",
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!info.isFile()) {
|
|
165
|
+
errors.push(`attachment ${id === "(unknown)" ? displayPath : id} is not a regular file or directory`);
|
|
166
|
+
diagnostics.push({
|
|
167
|
+
index,
|
|
168
|
+
id,
|
|
169
|
+
kind,
|
|
170
|
+
path: displayPath,
|
|
171
|
+
mime: declaredMime ?? null,
|
|
172
|
+
stage: "unsupported_filesystem_entry",
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const mime = declaredMime || IMAGE_MIME_BY_EXT[extname(realPath).toLowerCase()];
|
|
177
|
+
if (attachment.kind === "image") {
|
|
178
|
+
hasStructuredImageAttachments = true;
|
|
179
|
+
if (!includeImageBytes || attachment.vision?.include === false) {
|
|
180
|
+
textBlocks.push(formatFileMetadata(attachment, displayPath, realPath, info.size, mime));
|
|
181
|
+
diagnostics.push({
|
|
182
|
+
index,
|
|
183
|
+
id,
|
|
184
|
+
kind,
|
|
185
|
+
path: displayPath,
|
|
186
|
+
mime: mime ?? null,
|
|
187
|
+
stage: "image_metadata_only",
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (!mime ||
|
|
192
|
+
!mime.startsWith("image/") ||
|
|
193
|
+
!IMAGE_MIME_BY_EXT[extname(realPath).toLowerCase()]) {
|
|
194
|
+
errors.push(`image attachment ${id === "(unknown)" ? displayPath : id} has unsupported image type`);
|
|
195
|
+
diagnostics.push({
|
|
196
|
+
index,
|
|
197
|
+
id,
|
|
198
|
+
kind,
|
|
199
|
+
path: displayPath,
|
|
200
|
+
mime: mime ?? null,
|
|
201
|
+
stage: "unsupported_image_type",
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const diagnostic = {
|
|
206
|
+
index,
|
|
207
|
+
id,
|
|
208
|
+
kind,
|
|
209
|
+
path: displayPath,
|
|
210
|
+
mime,
|
|
211
|
+
stage: "image_pending_bytes",
|
|
212
|
+
};
|
|
213
|
+
pendingImages.push({
|
|
214
|
+
attachment,
|
|
215
|
+
displayPath,
|
|
216
|
+
realPath,
|
|
217
|
+
mime,
|
|
218
|
+
size: info.size,
|
|
219
|
+
diagnostic,
|
|
220
|
+
});
|
|
221
|
+
diagnostics.push(diagnostic);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
textBlocks.push(formatFileMetadata(attachment, displayPath, realPath, info.size, mime));
|
|
225
|
+
diagnostics.push({
|
|
226
|
+
index,
|
|
227
|
+
id,
|
|
228
|
+
kind,
|
|
229
|
+
path: displayPath,
|
|
230
|
+
mime: mime ?? null,
|
|
231
|
+
stage: "file_metadata",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (errors.length === 0 && includeImageBytes && pendingImages.length > 0) {
|
|
235
|
+
const verdict = enforceImageBytePolicy(pendingImages.map((image) => ({
|
|
236
|
+
name: image.attachment.originalName || image.displayPath,
|
|
237
|
+
mime: image.mime,
|
|
238
|
+
bytes: image.size,
|
|
239
|
+
})));
|
|
240
|
+
if (!verdict.ok) {
|
|
241
|
+
errors.push(`image attachment size policy failed: ${verdict.message}`);
|
|
242
|
+
for (const diagnostic of diagnostics) {
|
|
243
|
+
if (diagnostic.stage === "image_pending_bytes") {
|
|
244
|
+
diagnostic.stage = "image_size_policy_failed";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
for (const image of pendingImages) {
|
|
250
|
+
let bytes;
|
|
251
|
+
try {
|
|
252
|
+
bytes = await readFile(image.realPath);
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
errors.push(`image attachment ${image.attachment.id || image.displayPath} read failed: ${err.message}`);
|
|
256
|
+
image.diagnostic.stage = "image_read_failed";
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
260
|
+
const base64 = bytes.toString("base64");
|
|
261
|
+
images.push({
|
|
262
|
+
mime: image.mime,
|
|
263
|
+
name: image.attachment.originalName || image.displayPath,
|
|
264
|
+
dataUrl: `data:${image.mime};base64,${base64}`,
|
|
265
|
+
base64,
|
|
266
|
+
path: image.displayPath,
|
|
267
|
+
hash: `sha256:${sha256}`,
|
|
268
|
+
size: image.size,
|
|
269
|
+
origin: image.attachment.origin,
|
|
270
|
+
sessionId: image.attachment.sessionId,
|
|
271
|
+
});
|
|
272
|
+
image.diagnostic.stage = "image_bytes_loaded";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
logger.info("engine.run.input_attachments.complete", {
|
|
277
|
+
attachmentCount: attachments.length,
|
|
278
|
+
textBlockCount: textBlocks.length,
|
|
279
|
+
imageCount: images.length,
|
|
280
|
+
errorCount: errors.length,
|
|
281
|
+
attachments: diagnostics,
|
|
282
|
+
});
|
|
283
|
+
return { text: textBlocks.join("\n\n"), images, errors, hasStructuredImageAttachments };
|
|
284
|
+
}
|
|
285
|
+
function resolveAttachmentPath(attachment, cwd, displayPath) {
|
|
286
|
+
const visionPath = attachment.vision && typeof attachment.vision === "object"
|
|
287
|
+
? stringField(attachment.vision.mediaPath)
|
|
288
|
+
: undefined;
|
|
289
|
+
const candidate = visionPath ??
|
|
290
|
+
firstString(attachment.absPath, attachment.relPath, attachment.path) ??
|
|
291
|
+
displayPath;
|
|
292
|
+
if (isAbsolute(candidate))
|
|
293
|
+
return candidate;
|
|
294
|
+
return resolve(cwd, candidate);
|
|
295
|
+
}
|
|
296
|
+
function firstString(...values) {
|
|
297
|
+
return values.find((value) => typeof value === "string" && value.length > 0);
|
|
298
|
+
}
|
|
299
|
+
function stringField(value) {
|
|
300
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
301
|
+
}
|
|
302
|
+
async function validateStagedAttachmentPath(attachment, realPath, cwdReal, expectedSessionId, displayPath) {
|
|
303
|
+
if (!isStagedAttachmentReference(attachment, realPath, cwdReal))
|
|
304
|
+
return undefined;
|
|
305
|
+
const expectedDir = resolve(cwdReal, ".code-shell", "attachments", expectedSessionId);
|
|
306
|
+
let expectedDirReal;
|
|
307
|
+
try {
|
|
308
|
+
expectedDirReal = await realpath(expectedDir);
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
return `attachment ${attachment.id || displayPath} staged session directory is unavailable: ${err.message}`;
|
|
312
|
+
}
|
|
313
|
+
if (isPathInside(realPath, expectedDirReal))
|
|
314
|
+
return undefined;
|
|
315
|
+
return `attachment ${attachment.id || displayPath} staged path is outside .code-shell/attachments/${expectedSessionId}`;
|
|
316
|
+
}
|
|
317
|
+
function isStagedAttachmentReference(attachment, realPath, cwdReal) {
|
|
318
|
+
const fields = [
|
|
319
|
+
attachment.path,
|
|
320
|
+
attachment.absPath,
|
|
321
|
+
attachment.relPath,
|
|
322
|
+
attachment.vision?.mediaPath,
|
|
323
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
324
|
+
if (fields.some((value) => looksLikeStagedAttachmentPath(value)))
|
|
325
|
+
return true;
|
|
326
|
+
return isPathInside(realPath, resolve(cwdReal, ".code-shell", "attachments"));
|
|
327
|
+
}
|
|
328
|
+
function looksLikeStagedAttachmentPath(value) {
|
|
329
|
+
const normalized = normalizePath(value);
|
|
330
|
+
return (normalized === ".code-shell/attachments" ||
|
|
331
|
+
normalized.startsWith(".code-shell/attachments/") ||
|
|
332
|
+
normalized.includes("/.code-shell/attachments/"));
|
|
333
|
+
}
|
|
334
|
+
function isPathInside(child, parent) {
|
|
335
|
+
const rel = relative(parent, child);
|
|
336
|
+
return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
|
|
337
|
+
}
|
|
338
|
+
function isSafeSessionPathSegment(sessionId) {
|
|
339
|
+
if (!sessionId)
|
|
340
|
+
return false;
|
|
341
|
+
return (!sessionId.includes("/") && !sessionId.includes("\\") && sessionId !== "." && sessionId !== "..");
|
|
342
|
+
}
|
|
343
|
+
function normalizePath(value) {
|
|
344
|
+
return value.replace(/\\/g, "/");
|
|
345
|
+
}
|
|
346
|
+
function formatFileMetadata(attachment, displayPath, realPath, size, mime) {
|
|
347
|
+
const lines = [
|
|
348
|
+
`<attached-file path="${escapeText(displayPath)}">`,
|
|
349
|
+
`absolutePath: ${realPath}`,
|
|
350
|
+
...(mime ? [`mime: ${mime}`] : []),
|
|
351
|
+
`size: ${size}`,
|
|
352
|
+
`sha256: ${attachment.sha256}`,
|
|
353
|
+
`origin: ${attachment.origin}`,
|
|
354
|
+
`</attached-file>`,
|
|
355
|
+
];
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|
|
358
|
+
async function directoryTree(dir, cwdReal) {
|
|
359
|
+
const lines = [];
|
|
360
|
+
let entryCount = 0;
|
|
361
|
+
let truncated = false;
|
|
362
|
+
async function walk(current, depth) {
|
|
363
|
+
if (entryCount >= MAX_DIRECTORY_TREE_ENTRIES) {
|
|
364
|
+
truncated = true;
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
368
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
369
|
+
for (const entry of entries) {
|
|
370
|
+
if (entryCount >= MAX_DIRECTORY_TREE_ENTRIES) {
|
|
371
|
+
truncated = true;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (entry.name === ".git" || entry.name === "node_modules")
|
|
375
|
+
continue;
|
|
376
|
+
const abs = join(current, entry.name);
|
|
377
|
+
const real = await realpath(abs).catch(() => null);
|
|
378
|
+
if (!real)
|
|
379
|
+
continue;
|
|
380
|
+
const rel = relative(cwdReal, real);
|
|
381
|
+
if (rel === ".." || rel.startsWith(`..${sep}`))
|
|
382
|
+
continue;
|
|
383
|
+
entryCount += 1;
|
|
384
|
+
lines.push(`${" ".repeat(depth)}${entry.isDirectory() ? "dir " : "file "}${slash(rel)}`);
|
|
385
|
+
if (entry.isDirectory() && depth + 1 < MAX_DIRECTORY_TREE_DEPTH) {
|
|
386
|
+
await walk(real, depth + 1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
await walk(dir, 0);
|
|
391
|
+
return { lines, truncated, entryCount };
|
|
392
|
+
}
|
|
393
|
+
function escapeText(value) {
|
|
394
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
395
|
+
}
|
|
396
|
+
function slash(value) {
|
|
397
|
+
return sep === "\\" ? value.replace(/\\/g, "/") : value;
|
|
398
|
+
}
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import type { LLMClientBase } from "../llm/client-base.js";
|
|
5
5
|
import type { Message, ToolDefinition, LLMResponse, StreamCallback } from "../types.js";
|
|
6
6
|
import { Transcript } from "../session/transcript.js";
|
|
7
|
+
export interface ModelCallRecordingOptions {
|
|
8
|
+
sensitiveToolResultRedactions?: ReadonlyMap<string, string>;
|
|
9
|
+
}
|
|
7
10
|
/**
|
|
8
11
|
* Prompt-cache hit rate for one request, as CC computes it:
|
|
9
12
|
* cacheRead / (cacheRead + cacheCreation + uncachedInput)
|
|
@@ -18,11 +21,11 @@ export declare class ModelFacade {
|
|
|
18
21
|
private readonly client;
|
|
19
22
|
private readonly transcript;
|
|
20
23
|
constructor(client: LLMClientBase, transcript: Transcript);
|
|
21
|
-
call(systemPrompt: string, messages: Message[], tools: ToolDefinition[], onStream?: StreamCallback, signal?: AbortSignal): Promise<LLMResponse>;
|
|
24
|
+
call(systemPrompt: string, messages: Message[], tools: ToolDefinition[], onStream?: StreamCallback, signal?: AbortSignal, recordingOptions?: ModelCallRecordingOptions): Promise<LLMResponse>;
|
|
22
25
|
/**
|
|
23
26
|
* Call without streaming — used as fallback when streaming fails.
|
|
24
27
|
*/
|
|
25
|
-
callWithoutStreaming(systemPrompt: string, messages: Message[], tools: ToolDefinition[], signal?: AbortSignal): Promise<LLMResponse>;
|
|
28
|
+
callWithoutStreaming(systemPrompt: string, messages: Message[], tools: ToolDefinition[], signal?: AbortSignal, recordingOptions?: ModelCallRecordingOptions): Promise<LLMResponse>;
|
|
26
29
|
/** Record API timing and token usage to bootstrap state. */
|
|
27
30
|
private recordUsage;
|
|
28
31
|
/** Optional summarize function for tool use summaries. */
|
|
@@ -30,7 +30,7 @@ export class ModelFacade {
|
|
|
30
30
|
this.client = client;
|
|
31
31
|
this.transcript = transcript;
|
|
32
32
|
}
|
|
33
|
-
async call(systemPrompt, messages, tools, onStream, signal) {
|
|
33
|
+
async call(systemPrompt, messages, tools, onStream, signal, recordingOptions) {
|
|
34
34
|
const startMs = Date.now();
|
|
35
35
|
const msgCount = messages.length;
|
|
36
36
|
const sid = getCurrentSid();
|
|
@@ -42,7 +42,7 @@ export class ModelFacade {
|
|
|
42
42
|
// Image base64 payloads stay out of recorded prompts — see
|
|
43
43
|
// logging/sanitize-messages.ts. Transcripts keep the full bytes
|
|
44
44
|
// (needed for replay); logs do not.
|
|
45
|
-
messages: sanitizeMessages(messages),
|
|
45
|
+
messages: sanitizeMessages(messages, recordingOptions),
|
|
46
46
|
tools,
|
|
47
47
|
systemPrompt,
|
|
48
48
|
}, reqId);
|
|
@@ -111,7 +111,7 @@ export class ModelFacade {
|
|
|
111
111
|
/**
|
|
112
112
|
* Call without streaming — used as fallback when streaming fails.
|
|
113
113
|
*/
|
|
114
|
-
async callWithoutStreaming(systemPrompt, messages, tools, signal) {
|
|
114
|
+
async callWithoutStreaming(systemPrompt, messages, tools, signal, recordingOptions) {
|
|
115
115
|
const startMs = Date.now();
|
|
116
116
|
const msgCount = messages.length;
|
|
117
117
|
const sid = getCurrentSid();
|
|
@@ -122,7 +122,7 @@ export class ModelFacade {
|
|
|
122
122
|
stream: false,
|
|
123
123
|
// Image base64 payloads stay out of recorded prompts — same rule
|
|
124
124
|
// as the streaming path above.
|
|
125
|
-
messages: sanitizeMessages(messages),
|
|
125
|
+
messages: sanitizeMessages(messages, recordingOptions),
|
|
126
126
|
tools,
|
|
127
127
|
systemPrompt,
|
|
128
128
|
}, reqId);
|
|
@@ -29,6 +29,16 @@ export interface ParsedImage {
|
|
|
29
29
|
dataUrl: string;
|
|
30
30
|
/** Just the base64 payload, with the data-URL prefix stripped. */
|
|
31
31
|
base64: string;
|
|
32
|
+
/** Stable on-disk path, usually cwd-relative for staged desktop attachments. */
|
|
33
|
+
path?: string;
|
|
34
|
+
/** Wire hash attr, usually `sha256:<hex>`. */
|
|
35
|
+
hash?: string;
|
|
36
|
+
/** Size in bytes from wire metadata. Core must still verify with stat when reading. */
|
|
37
|
+
size?: number;
|
|
38
|
+
/** Source channel that produced this attachment. */
|
|
39
|
+
origin?: string;
|
|
40
|
+
/** Session directory that owns the staged attachment. */
|
|
41
|
+
sessionId?: string;
|
|
32
42
|
}
|
|
33
43
|
export interface ParsedTask {
|
|
34
44
|
/** Remaining plain-text portion (image blocks removed, surrounding whitespace trimmed). */
|
|
@@ -117,6 +117,11 @@ export function parseTaskWithImages(task) {
|
|
|
117
117
|
name: attrs.name ?? "",
|
|
118
118
|
dataUrl: `data:${finalMime};base64,${base64}`,
|
|
119
119
|
base64,
|
|
120
|
+
...(attrs.path ? { path: attrs.path } : {}),
|
|
121
|
+
...(attrs.hash ? { hash: attrs.hash } : {}),
|
|
122
|
+
...(attrs.size && Number.isFinite(Number(attrs.size)) ? { size: Number(attrs.size) } : {}),
|
|
123
|
+
...(attrs.origin ? { origin: attrs.origin } : {}),
|
|
124
|
+
...(attrs.sessionid ? { sessionId: attrs.sessionid } : {}),
|
|
120
125
|
});
|
|
121
126
|
return "";
|
|
122
127
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InputAttachmentMeta } from "../protocol/types.js";
|
|
2
|
+
import type { ContentBlock, LLMConfig } from "../types.js";
|
|
3
|
+
import { type ParsedTask } from "./parse-task.js";
|
|
4
|
+
import type { EngineResult } from "./types.js";
|
|
5
|
+
export interface PreparedRunImageInput {
|
|
6
|
+
parsedTask: ParsedTask;
|
|
7
|
+
taskText: string;
|
|
8
|
+
}
|
|
9
|
+
export type PrepareRunImageInputResult = ({
|
|
10
|
+
ok: true;
|
|
11
|
+
} & PreparedRunImageInput) | {
|
|
12
|
+
ok: false;
|
|
13
|
+
result: EngineResult;
|
|
14
|
+
};
|
|
15
|
+
export declare function prepareRunImageInput(args: {
|
|
16
|
+
task: string;
|
|
17
|
+
cwd: string;
|
|
18
|
+
llm: Pick<LLMConfig, "provider" | "providerKind" | "model">;
|
|
19
|
+
sessionId?: string;
|
|
20
|
+
attachments?: readonly InputAttachmentMeta[];
|
|
21
|
+
}): Promise<PrepareRunImageInputResult>;
|
|
22
|
+
export declare function buildRunUserMessageContent(parsedTask: ParsedTask, cwd: string, taskText: string): string | ContentBlock[];
|