@code-yeongyu/senpi 2026.7.11 → 2026.7.13
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/CHANGELOG.md +17 -0
- package/dist/core/agent-session.d.ts +9 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +56 -15
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +76 -25
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js.map +1 -1
- package/dist/core/resource-loader.d.ts +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +20 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/modes/interactive/compaction-queue-transfer.d.ts +22 -0
- package/dist/modes/interactive/compaction-queue-transfer.d.ts.map +1 -0
- package/dist/modes/interactive/compaction-queue-transfer.js +82 -0
- package/dist/modes/interactive/compaction-queue-transfer.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +83 -69
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@babel/helper-string-parser/LICENSE +22 -0
- package/node_modules/@babel/helper-string-parser/README.md +19 -0
- package/node_modules/@babel/helper-string-parser/lib/index.js +295 -0
- package/node_modules/@babel/helper-string-parser/lib/index.js.map +1 -0
- package/node_modules/@babel/helper-string-parser/package.json +31 -0
- package/node_modules/@babel/helper-validator-identifier/LICENSE +22 -0
- package/node_modules/@babel/helper-validator-identifier/README.md +19 -0
- package/node_modules/@babel/helper-validator-identifier/lib/identifier.js +70 -0
- package/node_modules/@babel/helper-validator-identifier/lib/identifier.js.map +1 -0
- package/node_modules/@babel/helper-validator-identifier/lib/index.js +57 -0
- package/node_modules/@babel/helper-validator-identifier/lib/index.js.map +1 -0
- package/node_modules/@babel/helper-validator-identifier/lib/keyword.js +35 -0
- package/node_modules/@babel/helper-validator-identifier/lib/keyword.js.map +1 -0
- package/node_modules/@babel/helper-validator-identifier/package.json +31 -0
- package/node_modules/@babel/parser/LICENSE +19 -0
- package/node_modules/@babel/parser/README.md +19 -0
- package/node_modules/@babel/parser/bin/babel-parser.js +15 -0
- package/node_modules/@babel/parser/lib/index.js +14599 -0
- package/node_modules/@babel/parser/lib/index.js.map +1 -0
- package/node_modules/@babel/parser/package.json +50 -0
- package/node_modules/@babel/parser/typings/babel-parser.d.ts +262 -0
- package/node_modules/@babel/types/LICENSE +22 -0
- package/node_modules/@babel/types/README.md +19 -0
- package/node_modules/@babel/types/lib/asserts/assertNode.js +16 -0
- package/node_modules/@babel/types/lib/asserts/assertNode.js.map +1 -0
- package/node_modules/@babel/types/lib/asserts/generated/index.js +1251 -0
- package/node_modules/@babel/types/lib/asserts/generated/index.js.map +1 -0
- package/node_modules/@babel/types/lib/ast-types/generated/index.js +3 -0
- package/node_modules/@babel/types/lib/ast-types/generated/index.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js +18 -0
- package/node_modules/@babel/types/lib/builders/flow/createFlowUnionType.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js +31 -0
- package/node_modules/@babel/types/lib/builders/flow/createTypeAnnotationBasedOnTypeof.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/generated/index.js +29 -0
- package/node_modules/@babel/types/lib/builders/generated/index.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/generated/lowercase.js +2902 -0
- package/node_modules/@babel/types/lib/builders/generated/lowercase.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/generated/uppercase.js +272 -0
- package/node_modules/@babel/types/lib/builders/generated/uppercase.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/productions.js +12 -0
- package/node_modules/@babel/types/lib/builders/productions.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/react/buildChildren.js +24 -0
- package/node_modules/@babel/types/lib/builders/react/buildChildren.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js +22 -0
- package/node_modules/@babel/types/lib/builders/typescript/createTSUnionType.js.map +1 -0
- package/node_modules/@babel/types/lib/builders/validateNode.js +21 -0
- package/node_modules/@babel/types/lib/builders/validateNode.js.map +1 -0
- package/node_modules/@babel/types/lib/clone/clone.js +12 -0
- package/node_modules/@babel/types/lib/clone/clone.js.map +1 -0
- package/node_modules/@babel/types/lib/clone/cloneDeep.js +12 -0
- package/node_modules/@babel/types/lib/clone/cloneDeep.js.map +1 -0
- package/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js +12 -0
- package/node_modules/@babel/types/lib/clone/cloneDeepWithoutLoc.js.map +1 -0
- package/node_modules/@babel/types/lib/clone/cloneNode.js +107 -0
- package/node_modules/@babel/types/lib/clone/cloneNode.js.map +1 -0
- package/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js +12 -0
- package/node_modules/@babel/types/lib/clone/cloneWithoutLoc.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/addComment.js +15 -0
- package/node_modules/@babel/types/lib/comments/addComment.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/addComments.js +22 -0
- package/node_modules/@babel/types/lib/comments/addComments.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/inheritInnerComments.js +12 -0
- package/node_modules/@babel/types/lib/comments/inheritInnerComments.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/inheritLeadingComments.js +12 -0
- package/node_modules/@babel/types/lib/comments/inheritLeadingComments.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/inheritTrailingComments.js +12 -0
- package/node_modules/@babel/types/lib/comments/inheritTrailingComments.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/inheritsComments.js +17 -0
- package/node_modules/@babel/types/lib/comments/inheritsComments.js.map +1 -0
- package/node_modules/@babel/types/lib/comments/removeComments.js +15 -0
- package/node_modules/@babel/types/lib/comments/removeComments.js.map +1 -0
- package/node_modules/@babel/types/lib/constants/generated/index.js +60 -0
- package/node_modules/@babel/types/lib/constants/generated/index.js.map +1 -0
- package/node_modules/@babel/types/lib/constants/index.js +31 -0
- package/node_modules/@babel/types/lib/constants/index.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/ensureBlock.js +14 -0
- package/node_modules/@babel/types/lib/converters/ensureBlock.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js +65 -0
- package/node_modules/@babel/types/lib/converters/gatherSequenceExpressions.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toBindingIdentifierName.js +14 -0
- package/node_modules/@babel/types/lib/converters/toBindingIdentifierName.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toBlock.js +29 -0
- package/node_modules/@babel/types/lib/converters/toBlock.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toComputedKey.js +14 -0
- package/node_modules/@babel/types/lib/converters/toComputedKey.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toExpression.js +28 -0
- package/node_modules/@babel/types/lib/converters/toExpression.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toIdentifier.js +25 -0
- package/node_modules/@babel/types/lib/converters/toIdentifier.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toKeyAlias.js +38 -0
- package/node_modules/@babel/types/lib/converters/toKeyAlias.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toSequenceExpression.js +19 -0
- package/node_modules/@babel/types/lib/converters/toSequenceExpression.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/toStatement.js +39 -0
- package/node_modules/@babel/types/lib/converters/toStatement.js.map +1 -0
- package/node_modules/@babel/types/lib/converters/valueToNode.js +89 -0
- package/node_modules/@babel/types/lib/converters/valueToNode.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/core.js +1725 -0
- package/node_modules/@babel/types/lib/definitions/core.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/deprecated-aliases.js +11 -0
- package/node_modules/@babel/types/lib/definitions/deprecated-aliases.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/experimental.js +124 -0
- package/node_modules/@babel/types/lib/definitions/experimental.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/flow.js +495 -0
- package/node_modules/@babel/types/lib/definitions/flow.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/index.js +106 -0
- package/node_modules/@babel/types/lib/definitions/index.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/jsx.js +157 -0
- package/node_modules/@babel/types/lib/definitions/jsx.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/misc.js +31 -0
- package/node_modules/@babel/types/lib/definitions/misc.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/placeholders.js +27 -0
- package/node_modules/@babel/types/lib/definitions/placeholders.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/typescript.js +528 -0
- package/node_modules/@babel/types/lib/definitions/typescript.js.map +1 -0
- package/node_modules/@babel/types/lib/definitions/utils.js +296 -0
- package/node_modules/@babel/types/lib/definitions/utils.js.map +1 -0
- package/node_modules/@babel/types/lib/index-legacy.d.ts +2797 -0
- package/node_modules/@babel/types/lib/index.d.ts +3617 -0
- package/node_modules/@babel/types/lib/index.js +582 -0
- package/node_modules/@babel/types/lib/index.js.flow +2650 -0
- package/node_modules/@babel/types/lib/index.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/appendToMemberExpression.js +15 -0
- package/node_modules/@babel/types/lib/modifications/appendToMemberExpression.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js +65 -0
- package/node_modules/@babel/types/lib/modifications/flow/removeTypeDuplicates.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/inherits.js +28 -0
- package/node_modules/@babel/types/lib/modifications/inherits.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/prependToMemberExpression.js +17 -0
- package/node_modules/@babel/types/lib/modifications/prependToMemberExpression.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/removeProperties.js +24 -0
- package/node_modules/@babel/types/lib/modifications/removeProperties.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/removePropertiesDeep.js +14 -0
- package/node_modules/@babel/types/lib/modifications/removePropertiesDeep.js.map +1 -0
- package/node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js +66 -0
- package/node_modules/@babel/types/lib/modifications/typescript/removeTypeDuplicates.js.map +1 -0
- package/node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js +48 -0
- package/node_modules/@babel/types/lib/retrievers/getAssignmentIdentifiers.js.map +1 -0
- package/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js +102 -0
- package/node_modules/@babel/types/lib/retrievers/getBindingIdentifiers.js.map +1 -0
- package/node_modules/@babel/types/lib/retrievers/getFunctionName.js +63 -0
- package/node_modules/@babel/types/lib/retrievers/getFunctionName.js.map +1 -0
- package/node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js +13 -0
- package/node_modules/@babel/types/lib/retrievers/getOuterBindingIdentifiers.js.map +1 -0
- package/node_modules/@babel/types/lib/traverse/traverse.js +50 -0
- package/node_modules/@babel/types/lib/traverse/traverse.js.map +1 -0
- package/node_modules/@babel/types/lib/traverse/traverseFast.js +40 -0
- package/node_modules/@babel/types/lib/traverse/traverseFast.js.map +1 -0
- package/node_modules/@babel/types/lib/utils/deprecationWarning.js +44 -0
- package/node_modules/@babel/types/lib/utils/deprecationWarning.js.map +1 -0
- package/node_modules/@babel/types/lib/utils/inherit.js +13 -0
- package/node_modules/@babel/types/lib/utils/inherit.js.map +1 -0
- package/node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js +40 -0
- package/node_modules/@babel/types/lib/utils/react/cleanJSXElementLiteralChild.js.map +1 -0
- package/node_modules/@babel/types/lib/utils/shallowEqual.js +17 -0
- package/node_modules/@babel/types/lib/utils/shallowEqual.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js +13 -0
- package/node_modules/@babel/types/lib/validators/buildMatchMemberExpression.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/generated/index.js +2797 -0
- package/node_modules/@babel/types/lib/validators/generated/index.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/is.js +27 -0
- package/node_modules/@babel/types/lib/validators/is.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isBinding.js +27 -0
- package/node_modules/@babel/types/lib/validators/isBinding.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isBlockScoped.js +13 -0
- package/node_modules/@babel/types/lib/validators/isBlockScoped.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isImmutable.js +21 -0
- package/node_modules/@babel/types/lib/validators/isImmutable.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isLet.js +13 -0
- package/node_modules/@babel/types/lib/validators/isLet.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isNode.js +12 -0
- package/node_modules/@babel/types/lib/validators/isNode.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isNodesEquivalent.js +57 -0
- package/node_modules/@babel/types/lib/validators/isNodesEquivalent.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isPlaceholderType.js +15 -0
- package/node_modules/@babel/types/lib/validators/isPlaceholderType.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isReferenced.js +96 -0
- package/node_modules/@babel/types/lib/validators/isReferenced.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isScope.js +18 -0
- package/node_modules/@babel/types/lib/validators/isScope.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isSpecifierDefault.js +14 -0
- package/node_modules/@babel/types/lib/validators/isSpecifierDefault.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isType.js +17 -0
- package/node_modules/@babel/types/lib/validators/isType.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isValidES3Identifier.js +13 -0
- package/node_modules/@babel/types/lib/validators/isValidES3Identifier.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isValidIdentifier.js +18 -0
- package/node_modules/@babel/types/lib/validators/isValidIdentifier.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/isVar.js +15 -0
- package/node_modules/@babel/types/lib/validators/isVar.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/matchesPattern.js +44 -0
- package/node_modules/@babel/types/lib/validators/matchesPattern.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/react/isCompatTag.js +11 -0
- package/node_modules/@babel/types/lib/validators/react/isCompatTag.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/react/isReactComponent.js +11 -0
- package/node_modules/@babel/types/lib/validators/react/isReactComponent.js.map +1 -0
- package/node_modules/@babel/types/lib/validators/validate.js +42 -0
- package/node_modules/@babel/types/lib/validators/validate.js.map +1 -0
- package/node_modules/@babel/types/package.json +39 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +10 -2
- package/node_modules/@code-yeongyu/senpi-codemode/README.md +128 -78
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +15 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/reserved.ts +9 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +197 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/output-bridge.ts +96 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/schema-injection.ts +3 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/completion/handler.ts +95 -26
- package/node_modules/@code-yeongyu/senpi-codemode/src/completion/tool-bridge.ts +11 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/config/settings.ts +96 -8
- package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +24 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +155 -21
- package/node_modules/@code-yeongyu/senpi-codemode/src/interpreters/detect.ts +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +252 -48
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +263 -83
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +11 -5
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/local-module-loader.ts +108 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +8 -6
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/rewrite-imports.ts +164 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +6 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +307 -41
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +3 -2
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +792 -79
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +234 -21
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/runner.rb +154 -51
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +1 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/output/output-meta.ts +89 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/output/streaming-output.ts +296 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +107 -30
- package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/bridge-timeout.ts +2 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/timeouts/idle-timeout.ts +8 -19
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +175 -113
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +130 -120
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/image.ts +274 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/json-tree.ts +247 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/render.ts +641 -38
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/status-events.ts +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +29 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +16 -16
- package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +6 -6
- package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +6 -6
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext, ToolDefinition } from "@code-yeongyu/senpi";
|
|
2
|
-
import type { HostToKernelMessage, KernelToHostMessage } from "../bridge/protocol.ts";
|
|
3
4
|
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
5
|
+
import { defaultCodemodeSettings, type ResolvedCodemodeSettings } from "../config/settings.ts";
|
|
6
|
+
import type { EvalExecutionTracker } from "../extension/session-manager.ts";
|
|
4
7
|
import { buildEvalPrompt } from "../prompt/eval-prompt.ts";
|
|
8
|
+
import { TIMEOUT_PAUSE_OP, TIMEOUT_RESUME_OP } from "../timeouts/bridge-timeout.ts";
|
|
9
|
+
import { IdleTimeout } from "../timeouts/idle-timeout.ts";
|
|
5
10
|
import { CellHandler, type CellState } from "./cell-handler.ts";
|
|
6
|
-
import {
|
|
11
|
+
import type { EvalImageResizer } from "./image.ts";
|
|
7
12
|
import {
|
|
8
13
|
createEvalInputSchema,
|
|
9
14
|
type EnabledEvalLanguages,
|
|
15
|
+
type EvalInputSchema,
|
|
10
16
|
type EvalKernel,
|
|
11
17
|
type EvalKernelManager,
|
|
12
|
-
type EvalKernelRunInput,
|
|
13
18
|
type EvalToolDetails,
|
|
14
19
|
type EvalToolInput,
|
|
15
20
|
type ExecuteTool,
|
|
@@ -24,111 +29,86 @@ export interface CreateEvalToolOptions {
|
|
|
24
29
|
readonly cellTimeoutSeconds: number;
|
|
25
30
|
readonly executeTool: ExecuteTool;
|
|
26
31
|
readonly complete?: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
32
|
+
readonly settings?: ResolvedCodemodeSettings;
|
|
33
|
+
readonly artifactsDir?: string;
|
|
34
|
+
readonly imageResizer?: EvalImageResizer;
|
|
35
|
+
readonly executionTracker?: EvalExecutionTracker;
|
|
36
|
+
readonly proxyExecutor?: (params: EvalToolInput, signal?: AbortSignal) => Promise<AgentToolResult<EvalToolDetails>>;
|
|
37
|
+
readonly renderers?: Pick<ToolDefinition<EvalInputSchema, EvalToolDetails>, "renderCall" | "renderResult">;
|
|
38
|
+
/** Whether the task-tool spawn helpers (agent()/output()/<dag>) are advertised in the prompt. */
|
|
39
|
+
readonly spawns?: boolean;
|
|
40
|
+
/** Default agent name surfaced in the agent() helper docs when spawns are enabled. */
|
|
41
|
+
readonly spawnDefaultAgent?: string;
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
interface EvalCellInvocation {
|
|
30
45
|
readonly cellId: string;
|
|
31
46
|
readonly input: EvalToolInput;
|
|
32
|
-
readonly signal: AbortSignal
|
|
47
|
+
readonly signal: AbortSignal;
|
|
33
48
|
readonly onUpdate: AgentToolUpdateCallback<EvalToolDetails> | undefined;
|
|
34
49
|
readonly ctx: ExtensionContext;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
|
-
|
|
52
|
+
interface CellExecutionOptions {
|
|
53
|
+
readonly callerSignal: AbortSignal;
|
|
54
|
+
readonly cellId: string;
|
|
55
|
+
readonly onAbort: (error: Error) => void;
|
|
56
|
+
readonly timeoutMs: number;
|
|
57
|
+
}
|
|
38
58
|
|
|
39
59
|
const INTERRUPT_DELIVERY_GRACE_MS = 100;
|
|
40
60
|
|
|
41
|
-
class CellKernel implements EvalKernel {
|
|
42
|
-
readonly #kernel: EvalKernel;
|
|
43
|
-
readonly #state: CellState;
|
|
44
|
-
|
|
45
|
-
constructor(kernel: EvalKernel, state: CellState) {
|
|
46
|
-
this.#kernel = kernel;
|
|
47
|
-
this.#state = state;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
run(input: EvalKernelRunInput): Promise<Extract<KernelToHostMessage, { type: "result" }>> {
|
|
51
|
-
return this.#kernel.run(input);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
deliverToolReply(message: ToolReply): void {
|
|
55
|
-
if (this.#state.active) this.#kernel.deliverToolReply(message);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
interrupt(reason?: string): Promise<void> {
|
|
59
|
-
return this.#kernel.interrupt(reason);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
reset(): Promise<void> {
|
|
63
|
-
return this.#kernel.reset();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
close(): Promise<void> {
|
|
67
|
-
return this.#kernel.close();
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
61
|
class CellExecution {
|
|
72
|
-
readonly #callerSignal: AbortSignal
|
|
73
|
-
readonly #timeoutMs: number;
|
|
62
|
+
readonly #callerSignal: AbortSignal;
|
|
74
63
|
readonly #onAbort: (error: Error) => void;
|
|
75
64
|
readonly #abortPromise: Promise<never>;
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
65
|
+
readonly #watchdog: IdleTimeout;
|
|
66
|
+
#rejectAbort: ((reason?: unknown) => void) | undefined;
|
|
67
|
+
#kernel: EvalKernel | undefined;
|
|
68
|
+
#interruptDeadline: ReturnType<typeof setTimeout> | undefined;
|
|
80
69
|
#active = true;
|
|
81
70
|
|
|
82
|
-
constructor(
|
|
83
|
-
this.#callerSignal = callerSignal;
|
|
84
|
-
this.#
|
|
85
|
-
this.#onAbort = onAbort;
|
|
71
|
+
constructor(options: CellExecutionOptions) {
|
|
72
|
+
this.#callerSignal = options.callerSignal;
|
|
73
|
+
this.#onAbort = options.onAbort;
|
|
86
74
|
this.#abortPromise = new Promise<never>((_resolve, reject) => {
|
|
87
75
|
this.#rejectAbort = reject;
|
|
88
76
|
});
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
this.#watchdog = new IdleTimeout({
|
|
78
|
+
cellId: options.cellId,
|
|
79
|
+
timeoutMs: options.timeoutMs,
|
|
80
|
+
onTimeout: ({ error }) => this.#abort(error),
|
|
81
|
+
});
|
|
82
|
+
this.#callerSignal.addEventListener("abort", this.#handleCallerAbort, { once: true });
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
if (!this.#active) return;
|
|
96
|
-
this.#deadline = setTimeout(() => {
|
|
97
|
-
const error = new Error(`Cell timed out after ${this.#timeoutMs}ms`);
|
|
98
|
-
error.name = "TimeoutError";
|
|
99
|
-
this.#abort(error);
|
|
100
|
-
}, this.#timeoutMs);
|
|
85
|
+
pause(): void {
|
|
86
|
+
this.#watchdog.pause();
|
|
101
87
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (this.#deadline === undefined) return;
|
|
105
|
-
clearTimeout(this.#deadline);
|
|
106
|
-
this.#deadline = undefined;
|
|
88
|
+
resume(): void {
|
|
89
|
+
this.#watchdog.resume();
|
|
107
90
|
}
|
|
108
|
-
|
|
109
|
-
setKernel(kernel: CellKernel): void {
|
|
91
|
+
setKernel(kernel: EvalKernel): void {
|
|
110
92
|
this.#kernel = kernel;
|
|
111
93
|
}
|
|
112
|
-
|
|
113
94
|
cancel(reason: unknown): void {
|
|
114
95
|
this.#abort(reason);
|
|
115
96
|
}
|
|
116
|
-
|
|
117
|
-
async wait<T>(operation: Promise<T>): Promise<T> {
|
|
118
|
-
const guardedOperation = operation.then(
|
|
119
|
-
(value): T | Promise<never> => (this.#active ? value : this.#abortPromise),
|
|
120
|
-
(reason: unknown): Promise<never> => (this.#active ? Promise.reject(reason) : this.#abortPromise),
|
|
121
|
-
);
|
|
122
|
-
return await Promise.race([guardedOperation, this.#abortPromise]);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
97
|
finish(): void {
|
|
126
98
|
this.#active = false;
|
|
127
99
|
this.#cleanup();
|
|
128
100
|
}
|
|
129
101
|
|
|
102
|
+
async wait<Result>(operation: Promise<Result>): Promise<Result> {
|
|
103
|
+
const guarded = operation.then(
|
|
104
|
+
(value): Result | Promise<never> => (this.#active ? value : this.#abortPromise),
|
|
105
|
+
(reason: unknown): Promise<never> => (this.#active ? Promise.reject(reason) : this.#abortPromise),
|
|
106
|
+
);
|
|
107
|
+
return await Promise.race([guarded, this.#abortPromise]);
|
|
108
|
+
}
|
|
109
|
+
|
|
130
110
|
readonly #handleCallerAbort = (): void => {
|
|
131
|
-
this.#abort(this.#callerSignal
|
|
111
|
+
this.#abort(this.#callerSignal.reason);
|
|
132
112
|
};
|
|
133
113
|
|
|
134
114
|
#abort(reason: unknown): void {
|
|
@@ -138,11 +118,11 @@ class CellExecution {
|
|
|
138
118
|
const error = abortError(reason);
|
|
139
119
|
this.#onAbort(error);
|
|
140
120
|
const kernel = this.#kernel;
|
|
141
|
-
if (
|
|
121
|
+
if (kernel === undefined) {
|
|
142
122
|
this.#settleAbort(error);
|
|
143
123
|
return;
|
|
144
124
|
}
|
|
145
|
-
this.#
|
|
125
|
+
this.#interruptDeadline = setTimeout(() => this.#settleAbort(error), INTERRUPT_DELIVERY_GRACE_MS);
|
|
146
126
|
void Promise.resolve()
|
|
147
127
|
.then(() => kernel.interrupt(error.message))
|
|
148
128
|
.then(
|
|
@@ -152,23 +132,27 @@ class CellExecution {
|
|
|
152
132
|
}
|
|
153
133
|
|
|
154
134
|
#settleAbort(reason: unknown): void {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this
|
|
158
|
-
this.#
|
|
135
|
+
const reject = this.#rejectAbort;
|
|
136
|
+
if (reject === undefined) return;
|
|
137
|
+
this.#rejectAbort = undefined;
|
|
138
|
+
if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
|
|
139
|
+
reject(reason);
|
|
159
140
|
}
|
|
160
141
|
|
|
161
142
|
#cleanup(): void {
|
|
162
|
-
this.#callerSignal
|
|
163
|
-
this.
|
|
143
|
+
this.#callerSignal.removeEventListener("abort", this.#handleCallerAbort);
|
|
144
|
+
this.#watchdog.dispose();
|
|
145
|
+
if (this.#interruptDeadline !== undefined) clearTimeout(this.#interruptDeadline);
|
|
146
|
+
this.#interruptDeadline = undefined;
|
|
164
147
|
}
|
|
165
148
|
}
|
|
166
149
|
|
|
167
|
-
export function createEvalTool(
|
|
168
|
-
options: CreateEvalToolOptions,
|
|
169
|
-
): ToolDefinition<ReturnType<typeof createEvalInputSchema>, EvalToolDetails> {
|
|
150
|
+
export function createEvalTool(options: CreateEvalToolOptions): ToolDefinition<EvalInputSchema, EvalToolDetails> {
|
|
170
151
|
const parameters = createEvalInputSchema(options.enabledLanguages);
|
|
171
|
-
const prompt = buildEvalPrompt(options.enabledLanguages
|
|
152
|
+
const prompt = buildEvalPrompt(options.enabledLanguages, {
|
|
153
|
+
spawns: options.spawns ?? false,
|
|
154
|
+
...(options.spawnDefaultAgent === undefined ? {} : { spawnDefaultAgent: options.spawnDefaultAgent }),
|
|
155
|
+
});
|
|
172
156
|
const languages = enabledLanguageList(options.enabledLanguages);
|
|
173
157
|
return {
|
|
174
158
|
name: "eval",
|
|
@@ -178,15 +162,29 @@ export function createEvalTool(
|
|
|
178
162
|
promptGuidelines: [...prompt.promptGuidelines],
|
|
179
163
|
parameters,
|
|
180
164
|
executionMode: "sequential",
|
|
181
|
-
renderCall:
|
|
182
|
-
renderResult:
|
|
165
|
+
...(options.renderers?.renderCall === undefined ? {} : { renderCall: options.renderers.renderCall }),
|
|
166
|
+
...(options.renderers?.renderResult === undefined ? {} : { renderResult: options.renderers.renderResult }),
|
|
183
167
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
184
|
-
if (
|
|
185
|
-
|
|
168
|
+
if (options.proxyExecutor) return await options.proxyExecutor(params, signal);
|
|
169
|
+
if (!languages.includes(params.language))
|
|
170
|
+
throw new RangeError(
|
|
186
171
|
`Unsupported eval language "${params.language}". Enabled languages: ${languages.join(", ")}`,
|
|
187
172
|
);
|
|
188
|
-
|
|
189
|
-
|
|
173
|
+
options.executionTracker?.assertEvalExecutionAllowed();
|
|
174
|
+
const lifecycleController = new AbortController();
|
|
175
|
+
const combinedSignal = signal
|
|
176
|
+
? AbortSignal.any([signal, lifecycleController.signal])
|
|
177
|
+
: lifecycleController.signal;
|
|
178
|
+
const execution = runEvalCell(options, {
|
|
179
|
+
cellId: toolCallId,
|
|
180
|
+
input: params,
|
|
181
|
+
signal: combinedSignal,
|
|
182
|
+
onUpdate,
|
|
183
|
+
ctx,
|
|
184
|
+
});
|
|
185
|
+
return options.executionTracker
|
|
186
|
+
? await options.executionTracker.trackEvalExecution(execution, lifecycleController)
|
|
187
|
+
: await execution;
|
|
190
188
|
},
|
|
191
189
|
};
|
|
192
190
|
}
|
|
@@ -195,75 +193,87 @@ async function runEvalCell(
|
|
|
195
193
|
options: CreateEvalToolOptions,
|
|
196
194
|
invocation: EvalCellInvocation,
|
|
197
195
|
): Promise<AgentToolResult<EvalToolDetails>> {
|
|
198
|
-
if (invocation.signal
|
|
199
|
-
const timeoutMs = Math.floor((invocation.input.timeout ?? options.cellTimeoutSeconds) *
|
|
196
|
+
if (invocation.signal.aborted) throw abortError(invocation.signal.reason);
|
|
197
|
+
const timeoutMs = Math.floor((invocation.input.timeout ?? options.cellTimeoutSeconds) * 1_000);
|
|
200
198
|
const bridgeAbortController = new AbortController();
|
|
201
|
-
const cellSignal = invocation.signal
|
|
202
|
-
? AbortSignal.any([invocation.signal, bridgeAbortController.signal])
|
|
203
|
-
: bridgeAbortController.signal;
|
|
199
|
+
const cellSignal = AbortSignal.any([invocation.signal, bridgeAbortController.signal]);
|
|
204
200
|
const bridgeContext: ExtensionContext = { ...invocation.ctx, signal: cellSignal };
|
|
205
201
|
const state: CellState = {
|
|
206
|
-
|
|
207
|
-
language: invocation.input.language,
|
|
208
|
-
title: invocation.input.title,
|
|
202
|
+
input: invocation.input,
|
|
209
203
|
signal: cellSignal,
|
|
210
204
|
onUpdate: invocation.onUpdate,
|
|
211
205
|
toolCalls: [],
|
|
212
|
-
images: [],
|
|
213
206
|
pendingBridgeCalls: [],
|
|
207
|
+
statusEvents: [],
|
|
214
208
|
active: true,
|
|
215
209
|
output: "",
|
|
216
210
|
phase: undefined,
|
|
217
211
|
durationMs: 0,
|
|
212
|
+
status: "pending",
|
|
218
213
|
};
|
|
219
|
-
const execution = new CellExecution(
|
|
220
|
-
|
|
221
|
-
|
|
214
|
+
const execution = new CellExecution({
|
|
215
|
+
callerSignal: invocation.signal,
|
|
216
|
+
cellId: invocation.cellId,
|
|
217
|
+
onAbort: (error) => {
|
|
218
|
+
state.active = false;
|
|
219
|
+
bridgeAbortController.abort(error);
|
|
220
|
+
},
|
|
221
|
+
timeoutMs,
|
|
222
222
|
});
|
|
223
223
|
let handler: CellHandler | undefined;
|
|
224
224
|
try {
|
|
225
225
|
const acquired = await execution.wait(
|
|
226
226
|
options.kernelManager.getKernel(invocation.input.language, (message) => {
|
|
227
227
|
if (!state.active || !handler) return;
|
|
228
|
+
if (message.type === "status") {
|
|
229
|
+
if (message.event.op === TIMEOUT_PAUSE_OP) {
|
|
230
|
+
execution.pause();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (message.event.op === TIMEOUT_RESUME_OP) {
|
|
234
|
+
execution.resume();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
228
238
|
const pending = handler.handle(message);
|
|
229
239
|
void pending.catch((error: unknown) => execution.cancel(error));
|
|
230
240
|
}),
|
|
231
241
|
);
|
|
232
|
-
const kernel =
|
|
242
|
+
const kernel = acquired;
|
|
233
243
|
execution.setKernel(kernel);
|
|
234
|
-
execution.clearDeadline();
|
|
235
244
|
handler = new CellHandler(kernel, state, {
|
|
236
245
|
executeTool: options.executeTool,
|
|
237
|
-
|
|
246
|
+
settings: options.settings ?? defaultCodemodeSettings,
|
|
247
|
+
...(options.complete === undefined ? {} : { complete: options.complete }),
|
|
238
248
|
ctx: bridgeContext,
|
|
249
|
+
...(options.artifactsDir === undefined
|
|
250
|
+
? {}
|
|
251
|
+
: { artifactPath: join(options.artifactsDir, `eval-${randomUUID()}.log`) }),
|
|
252
|
+
...(options.imageResizer === undefined ? {} : { imageResizer: options.imageResizer }),
|
|
239
253
|
});
|
|
240
254
|
if ("setContext" in options.kernelManager && typeof options.kernelManager.setContext === "function") {
|
|
241
255
|
options.kernelManager.setContext(bridgeContext);
|
|
242
256
|
}
|
|
243
|
-
if (invocation.input.reset)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
);
|
|
251
|
-
if (result.ok && state.pendingBridgeCalls.length > 0) {
|
|
252
|
-
execution.startDeadline();
|
|
253
|
-
await execution.wait(Promise.all(state.pendingBridgeCalls));
|
|
254
|
-
execution.clearDeadline();
|
|
257
|
+
if (invocation.input.reset) await execution.wait(kernel.reset());
|
|
258
|
+
const result = await execution.wait(kernel.run({ cellId: invocation.cellId, code: invocation.input.code }));
|
|
259
|
+
if (result.ok && state.pendingBridgeCalls.length > 0) await execution.wait(Promise.all(state.pendingBridgeCalls));
|
|
260
|
+
return await handler.finalize(result);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (handler && error instanceof Error && error.name === "CodemodeSessionDisposedError") {
|
|
263
|
+
return await handler.finalizeCancellation(error);
|
|
255
264
|
}
|
|
256
|
-
|
|
265
|
+
throw error;
|
|
257
266
|
} finally {
|
|
258
267
|
state.active = false;
|
|
259
268
|
bridgeAbortController.abort();
|
|
260
269
|
execution.finish();
|
|
270
|
+
if (handler) await handler.flushOutput();
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
273
|
|
|
264
274
|
function abortError(reason: unknown): Error {
|
|
265
275
|
if (reason instanceof Error && reason.name !== "AbortError") return reason;
|
|
266
|
-
const error = new Error(typeof reason === "string" ? reason : "Eval interrupted");
|
|
276
|
+
const error = new Error(typeof reason === "string" ? reason : "Eval interrupted", { cause: reason });
|
|
267
277
|
error.name = "AbortError";
|
|
268
278
|
return error;
|
|
269
279
|
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import type { AgentToolResult, ExtensionContext } from "@code-yeongyu/senpi";
|
|
3
|
+
import { convertToPng, formatDimensionNote, resizeImage } from "@code-yeongyu/senpi";
|
|
4
|
+
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
5
|
+
import type { TruncationMeta } from "../output/output-meta.ts";
|
|
6
|
+
import {
|
|
7
|
+
artifactNotice,
|
|
8
|
+
DEFAULT_MAX_BYTES,
|
|
9
|
+
DEFAULT_MAX_LINES,
|
|
10
|
+
OutputSink,
|
|
11
|
+
type OutputSummary,
|
|
12
|
+
TailBuffer,
|
|
13
|
+
truncateTail,
|
|
14
|
+
} from "../output/streaming-output.ts";
|
|
15
|
+
|
|
16
|
+
const MAX_DISPLAY_TEXT_BYTES = 8_000;
|
|
17
|
+
|
|
18
|
+
export interface EvalImageContent {
|
|
19
|
+
readonly type: "image";
|
|
20
|
+
readonly data: string;
|
|
21
|
+
readonly mimeType: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface EvalImageResizeResult {
|
|
25
|
+
readonly image: EvalImageContent;
|
|
26
|
+
readonly dimensionNote?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type EvalImageResizer = (
|
|
30
|
+
image: EvalImageContent,
|
|
31
|
+
model: ExtensionContext["model"],
|
|
32
|
+
) => Promise<EvalImageResizeResult>;
|
|
33
|
+
|
|
34
|
+
export interface EvalOutputOptions {
|
|
35
|
+
readonly artifactPath?: string;
|
|
36
|
+
readonly headBytes: number;
|
|
37
|
+
readonly maxColumns: number;
|
|
38
|
+
readonly model: ExtensionContext["model"];
|
|
39
|
+
readonly imageResizer?: EvalImageResizer;
|
|
40
|
+
readonly onChunk: (aggregateText: string, cellText: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface EvalOutputResult {
|
|
44
|
+
readonly output: string;
|
|
45
|
+
readonly images: readonly EvalImageContent[];
|
|
46
|
+
readonly jsonOutputs: readonly unknown[];
|
|
47
|
+
readonly hasMarkdown: boolean;
|
|
48
|
+
readonly truncated: boolean;
|
|
49
|
+
readonly notice?: string;
|
|
50
|
+
readonly meta?: TruncationMeta;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
type DisplayMessage = Extract<KernelToHostMessage, { type: "display" }>;
|
|
54
|
+
type WebpModel = { readonly provider: string; readonly api: string } | undefined;
|
|
55
|
+
|
|
56
|
+
class DisplayPayloadError extends Error {
|
|
57
|
+
readonly name = "DisplayPayloadError";
|
|
58
|
+
|
|
59
|
+
constructor(mimeType: string, cause: SyntaxError) {
|
|
60
|
+
super(`Invalid ${mimeType} display payload: ${cause.message}`, { cause });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class EvalOutputCollector {
|
|
65
|
+
readonly #options: EvalOutputOptions;
|
|
66
|
+
readonly #sink: OutputSink;
|
|
67
|
+
readonly #aggregateTail = new TailBuffer(DEFAULT_MAX_BYTES * 2);
|
|
68
|
+
readonly #cellTail = new TailBuffer(DEFAULT_MAX_BYTES * 2);
|
|
69
|
+
readonly #displayImages: EvalImageContent[] = [];
|
|
70
|
+
readonly #images: EvalImageContent[] = [];
|
|
71
|
+
readonly #jsonOutputs: unknown[] = [];
|
|
72
|
+
#hasMarkdown = false;
|
|
73
|
+
#imagesProcessed = false;
|
|
74
|
+
|
|
75
|
+
constructor(options: EvalOutputOptions) {
|
|
76
|
+
this.#options = options;
|
|
77
|
+
this.#sink = new OutputSink({
|
|
78
|
+
artifactPath: options.artifactPath,
|
|
79
|
+
headBytes: options.headBytes,
|
|
80
|
+
maxColumns: options.maxColumns,
|
|
81
|
+
onChunk: (chunk) => {
|
|
82
|
+
this.#aggregateTail.append(chunk);
|
|
83
|
+
this.#cellTail.append(chunk);
|
|
84
|
+
options.onChunk(this.#aggregateTail.text(), this.#cellTail.text());
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
push(text: string): void {
|
|
90
|
+
this.#sink.push(text);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
display(message: DisplayMessage): void {
|
|
94
|
+
if (message.mimeType.startsWith("image/")) {
|
|
95
|
+
this.#displayImages.push({ type: "image", mimeType: message.mimeType, data: message.dataBase64 });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const text = Buffer.from(message.dataBase64, "base64").toString("utf8");
|
|
99
|
+
if (message.mimeType === "application/json") {
|
|
100
|
+
let value: unknown;
|
|
101
|
+
try {
|
|
102
|
+
value = JSON.parse(text);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error instanceof SyntaxError) throw new DisplayPayloadError(message.mimeType, error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
this.#jsonOutputs.push(value);
|
|
108
|
+
this.#sink.push(`display[${this.#jsonOutputs.length}]:\n${formatDisplayJson(value)}\n`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (message.mimeType === "text/markdown") this.#hasMarkdown = true;
|
|
112
|
+
this.#sink.push(text.endsWith("\n") ? text : `${text}\n`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
aggregateText(): string {
|
|
116
|
+
return this.#aggregateTail.text();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async finish(): Promise<EvalOutputResult> {
|
|
120
|
+
await this.#processImages();
|
|
121
|
+
const summary = await this.#finalSummary();
|
|
122
|
+
const meta = truncationMetaFromSummary(summary);
|
|
123
|
+
const notice = summary.artifactId === undefined ? undefined : artifactNotice(summary.artifactId);
|
|
124
|
+
return {
|
|
125
|
+
output: summary.output.trimEnd(),
|
|
126
|
+
images: [...this.#images],
|
|
127
|
+
jsonOutputs: [...this.#jsonOutputs],
|
|
128
|
+
hasMarkdown: this.#hasMarkdown,
|
|
129
|
+
truncated: summary.truncated,
|
|
130
|
+
...(notice === undefined ? {} : { notice }),
|
|
131
|
+
...(meta === undefined ? {} : { meta }),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async flush(): Promise<void> {
|
|
136
|
+
await this.#sink.dump();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async #processImages(): Promise<void> {
|
|
140
|
+
if (this.#imagesProcessed) return;
|
|
141
|
+
this.#imagesProcessed = true;
|
|
142
|
+
const resize = this.#options.imageResizer ?? resizeEvalImage;
|
|
143
|
+
for (const source of this.#displayImages) {
|
|
144
|
+
const resized = await resize(source, this.#options.model);
|
|
145
|
+
this.#images.push(resized.image);
|
|
146
|
+
const description = resized.dimensionNote ?? `[${resized.image.mimeType}]`;
|
|
147
|
+
this.#sink.push(`display image ${this.#images.length}: ${description}\n`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async #finalSummary(): Promise<OutputSummary> {
|
|
152
|
+
const summary = await this.#sink.dump();
|
|
153
|
+
if (summary.truncated || summary.totalLines <= DEFAULT_MAX_LINES) return summary;
|
|
154
|
+
const truncated = truncateTail(summary.output, {
|
|
155
|
+
maxLines: DEFAULT_MAX_LINES,
|
|
156
|
+
maxBytes: Number.MAX_SAFE_INTEGER,
|
|
157
|
+
});
|
|
158
|
+
let artifactId = summary.artifactId;
|
|
159
|
+
if (artifactId === undefined && this.#options.artifactPath !== undefined) {
|
|
160
|
+
await writeFile(this.#options.artifactPath, this.#aggregateTail.text(), "utf8");
|
|
161
|
+
artifactId = this.#options.artifactPath;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
...summary,
|
|
165
|
+
output: truncated.content,
|
|
166
|
+
truncated: true,
|
|
167
|
+
outputLines: truncated.outputLines,
|
|
168
|
+
outputBytes: truncated.outputBytes,
|
|
169
|
+
...(artifactId === undefined ? {} : { artifactId }),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function webpExclusionForModel(model: WebpModel): true | undefined {
|
|
175
|
+
if (model === undefined) return undefined;
|
|
176
|
+
return model.provider === "ollama" ||
|
|
177
|
+
model.provider === "ollama-cloud" ||
|
|
178
|
+
model.provider === "llama.cpp" ||
|
|
179
|
+
model.provider === "lm-studio" ||
|
|
180
|
+
model.provider === "local-server" ||
|
|
181
|
+
model.api === "ollama-chat"
|
|
182
|
+
? true
|
|
183
|
+
: undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const resizeEvalImage: EvalImageResizer = async (image, model) => {
|
|
187
|
+
const excludeWebP = webpExclusionForModel(model);
|
|
188
|
+
const forceWebpConversion = excludeWebP === true && image.mimeType === "image/webp";
|
|
189
|
+
const resized = await resizeImage(
|
|
190
|
+
Buffer.from(image.data, "base64"),
|
|
191
|
+
image.mimeType,
|
|
192
|
+
forceWebpConversion ? { maxBytes: Buffer.byteLength(image.data, "utf8") } : undefined,
|
|
193
|
+
);
|
|
194
|
+
let output: EvalImageContent =
|
|
195
|
+
resized === null ? image : { type: "image", data: resized.data, mimeType: resized.mimeType };
|
|
196
|
+
if (excludeWebP === true && output.mimeType === "image/webp") {
|
|
197
|
+
const converted = await convertToPng(output.data, output.mimeType);
|
|
198
|
+
if (converted === null)
|
|
199
|
+
throw new TypeError(`Unable to convert ${output.mimeType} display output for the active model`);
|
|
200
|
+
output = { type: "image", data: converted.data, mimeType: converted.mimeType };
|
|
201
|
+
}
|
|
202
|
+
const dimensionNote = resized === null ? undefined : formatDimensionNote(resized);
|
|
203
|
+
return { image: output, ...(dimensionNote === undefined ? {} : { dimensionNote }) };
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
function formatDisplayJson(value: unknown): string {
|
|
207
|
+
let text: string;
|
|
208
|
+
try {
|
|
209
|
+
text = JSON.stringify(value, null, 2) ?? String(value);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (!(error instanceof TypeError)) throw error;
|
|
212
|
+
text = String(value);
|
|
213
|
+
}
|
|
214
|
+
if (text.length <= MAX_DISPLAY_TEXT_BYTES) return text;
|
|
215
|
+
return `${text.slice(0, MAX_DISPLAY_TEXT_BYTES)}\n[…${text.length - MAX_DISPLAY_TEXT_BYTES}ch elided…]`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function truncationMetaFromSummary(summary: OutputSummary): TruncationMeta | undefined {
|
|
219
|
+
if (!summary.truncated) return undefined;
|
|
220
|
+
const artifact = summary.artifactId === undefined ? {} : { artifactId: summary.artifactId };
|
|
221
|
+
if (summary.elidedBytes !== undefined && summary.elidedBytes > 0) {
|
|
222
|
+
const elidedLines = summary.elidedLines ?? Math.max(0, summary.totalLines - summary.outputLines);
|
|
223
|
+
const keptLines = Math.max(0, summary.outputLines - 1);
|
|
224
|
+
const headLines = Math.ceil(keptLines / 2);
|
|
225
|
+
const tailLines = keptLines - headLines;
|
|
226
|
+
return {
|
|
227
|
+
direction: "middle",
|
|
228
|
+
truncatedBy: "middle",
|
|
229
|
+
totalLines: summary.totalLines,
|
|
230
|
+
totalBytes: summary.totalBytes,
|
|
231
|
+
outputLines: summary.outputLines,
|
|
232
|
+
outputBytes: summary.outputBytes,
|
|
233
|
+
...(headLines > 0 ? { headRange: { start: 1, end: headLines } } : {}),
|
|
234
|
+
...(tailLines > 0
|
|
235
|
+
? { tailRange: { start: summary.totalLines - tailLines + 1, end: summary.totalLines } }
|
|
236
|
+
: {}),
|
|
237
|
+
elidedBytes: summary.elidedBytes,
|
|
238
|
+
elidedLines,
|
|
239
|
+
...artifact,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
direction: "tail",
|
|
244
|
+
truncatedBy: summary.outputBytes < summary.totalBytes ? "bytes" : "lines",
|
|
245
|
+
totalLines: summary.totalLines,
|
|
246
|
+
totalBytes: summary.totalBytes,
|
|
247
|
+
outputLines: summary.outputLines,
|
|
248
|
+
outputBytes: summary.outputBytes,
|
|
249
|
+
shownRange: { start: Math.max(1, summary.totalLines - summary.outputLines + 1), end: summary.totalLines },
|
|
250
|
+
...artifact,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function marshalToolResult(result: AgentToolResult<unknown>) {
|
|
255
|
+
const texts = result.content.filter((part) => part.type === "text").map((part) => part.text);
|
|
256
|
+
const images = result.content
|
|
257
|
+
.filter((part) => part.type === "image")
|
|
258
|
+
.map((part) => ({ mimeType: part.mimeType, dataBase64: part.data }));
|
|
259
|
+
const details =
|
|
260
|
+
typeof result.details === "object" &&
|
|
261
|
+
result.details !== null &&
|
|
262
|
+
!Array.isArray(result.details) &&
|
|
263
|
+
Object.keys(result.details).length === 0
|
|
264
|
+
? undefined
|
|
265
|
+
: result.details;
|
|
266
|
+
const hasError = toolResultIsError(result);
|
|
267
|
+
const text = texts.join("\n");
|
|
268
|
+
return images.length === 0 && details === undefined && !hasError ? { text } : { text, details, images, hasError };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function toolResultIsError(result: AgentToolResult<unknown>): boolean {
|
|
272
|
+
const details = result.details;
|
|
273
|
+
return typeof details === "object" && details !== null && "isError" in details && details.isError === true;
|
|
274
|
+
}
|