/** * DSH tool factory adapter — implements IToolFactory by compiling canonical * rolebox tool definitions (zod args + ToolResult) into register-ready * `@deepseek-ai/dsh-tools` `ToolDefinition` inputs (`DshToolDefinition`). * * ── Contract basis ───────────────────────────────────────────────────────── * Verified against docs/dsh-plugin-contract.md §3.2-§3.5 (tarball citations: * `dsh-tools/lib/types/index.d.ts:106-124` ToolDefinition register input; * `dsh-tools/lib/types/schema.d.ts:177-239` defineTool/DefineToolOptions; * `dsh-tools/lib/types/schema.d.ts:9-84` ParameterSchemaSpec/ValueSchemaSpec * DSL; `dsh-tools/lib/types/index.d.ts:97-108` ToolOutputDefinition.render; * `dsh-tools/lib/types/index.d.ts:196-220` ToolExecutionInput.signal). * * ── register-ready raw JSON Schema (parameters AND output.schema) ─────────── * The dsh plugin registers each compiled tool via `ctx.tools.register(def)` * DIRECTLY — it does NOT wrap the definition in `defineTool()` (the adapter * MUST NOT import `@deepseek-ai/dsh-tools`). This is load-bearing: unlike * `defineTool()`, `register()` performs NO `parameters` compilation — verified * at source, it only asserts `output.schema` and then stores the definition * object as-is (dsh-tools lib/index.js:2755-2763). Only `defineTool()` * (lib/index.js:836-845) compiles the author DSL into raw JSON Schema via * `parameterSchemaSpecToJsonSchema`. * * Therefore BOTH `parameters` and `output.schema` MUST already be standard * JSON Schema (the enforced raw subset, §3.3): object/array/string/number/ * integer/boolean/null types, annotation-only `{}` = unconstrained JSON. * Downstream consumers read them as JSON Schema — the LLM request serializer * onto the wire, and Code Mode's SDK renderers (`jsonSchemaToTs`, * lib/index.js:1613/:2320). Emitting the author DSL here (per-property * `required:true` with no top-level `required` array) leaked onto the wire and * DeepSeek rejected the request with HTTP 400 "Invalid schema for function …". * * Consequently this adapter emits register-ready raw JSON Schema for * `parameters` by compiling its internal author-DSL mapping exactly as * `defineTool()` would (see `dslParameterMapToJsonSchema`), and the * annotation-only `{}` for the heterogeneous canonical ToolResult * `output.schema`. * * The zod args → DSL mapping is hand-rolled against the documented DSL subset * and verified against the installed zod@4 runtime: each zod node maps to the * closest DSL node (string/number/integer/boolean/null/array/object/json/oneOf * with per-property `required: true`, `description`, `enum`, `const`, `items`, * `additionalProperties`, and `default` annotations where expressible). * Unrepresentable zod constructs (tuple, intersection, date, lazy) degrade * to the `json` node (unconstrained lossless JSON) — documented, never * rejected. * * ── Imports ──────────────────────────────────────────────────────────────── * `@deepseek-ai/dsh-tools` is NOT a build-time dependency of this repo (the * dsh host provides it at runtime, pinned 0.1.5-rc.1). Following the Pi * adapter precedent (`src/platform/adapters/pi/tool-factory.ts` uses loose * typing for its optional peer dependency), this adapter emits * structurally-compatible plain objects and defines local structural types * mirroring the documented DSL. The returned object is opaque per * `IToolFactory` ("only the platform runtime interprets it"); the dsh plugin * layer registers it directly with `ctx.tools.register(compiled)`. * * MUST NOT import any package from the opencode platform SDK or the * deepseek dsh-tools SDK. */ import { z } from "zod"; import type { IToolFactory } from "../../ports/tool-factory.ts"; import type { DshContentBlock } from "./agent-registrar.ts"; import type { CanonicalToolDef } from "../../types.ts"; /** Annotation keywords shared by every author-facing value-schema node * (dsh-tools schema.d.ts:11-19). Non-validating. */ export interface DshValueSchemaAnnotations { description?: string; title?: string; default?: unknown; examples?: unknown; } /** * Author-facing value-schema DSL — a discriminated union mirroring the real * `ValueSchemaSpec` (dsh-tools schema.d.ts:20-72). Modelled as a union rather * than a flat bag of optional fields so the `object` variant's * `additionalProperties` is REQUIRED exactly as the harness declares it * (schema.d.ts:56-61) — a flat optional field let an object node omit the * openness that rc.6 mandates. */ export interface DshStringValueSchema extends DshValueSchemaAnnotations { type: "string"; enum?: readonly string[]; const?: string; } export interface DshNumberValueSchema extends DshValueSchemaAnnotations { type: "number"; enum?: readonly number[]; const?: number; } export interface DshIntegerValueSchema extends DshValueSchemaAnnotations { type: "integer"; enum?: readonly number[]; const?: number; } export interface DshBooleanValueSchema extends DshValueSchemaAnnotations { type: "boolean"; enum?: readonly boolean[]; const?: boolean; } export interface DshNullValueSchema extends DshValueSchemaAnnotations { type: "null"; enum?: readonly null[]; const?: null; } export interface DshArrayValueSchema extends DshValueSchemaAnnotations { type: "array"; /** Item schema; absent accepts any JSON item. */ items?: DshValueSchemaSpec; } export interface DshObjectValueSchema extends DshValueSchemaAnnotations { type: "object"; /** Per-property schema. */ properties?: DshParameterSchemaSpec; /** Object openness (`false` rejects undeclared keys) — REQUIRED (rc.6). */ additionalProperties: boolean; } export interface DshJsonValueSchema extends DshValueSchemaAnnotations { type: "json"; } export interface DshOneOfValueSchema extends DshValueSchemaAnnotations { /** Exact-one union; ≥2 branches. */ oneOf: readonly DshValueSchemaSpec[]; } /** One author-facing value-schema node for any lossless JSON value root. */ export type DshValueSchemaSpec = DshStringValueSchema | DshNumberValueSchema | DshIntegerValueSchema | DshBooleanValueSchema | DshNullValueSchema | DshArrayValueSchema | DshObjectValueSchema | DshJsonValueSchema | DshOneOfValueSchema; /** * One implicit parameter-root property: a value spec plus per-property * requiredness (dsh-tools schema.d.ts:74-76). Requiredness is NEVER a * top-level `required` array in the DSL. */ export type DshParameterPropertySpec = DshValueSchemaSpec & { required?: true; }; /** * Tool parameter schema — an implicit open object root keyed by property * name (dsh-tools schema.d.ts:81-84). This is the INTERNAL author DSL the * zod mapper produces; it is NOT what `register()` consumes. Compile it to * `DshJsonSchema` with `dslParameterMapToJsonSchema()` first. */ export type DshParameterSchemaSpec = { [key: string]: DshParameterPropertySpec; }; /** * Standard JSON Schema (the enforced raw subset, dsh-tools contract §3.3). * This is what `register()` stores, the LLM wire serializes, and Code Mode's * SDK renderers read. Annotation-only `{}` is the unconstrained-JSON form. */ export interface DshJsonSchema { type?: "string" | "number" | "integer" | "boolean" | "null" | "array" | "object"; /** Property schemas for `type: "object"`. */ properties?: Record; /** REQUIRED top-level array (never per-property) — lifted from the DSL. */ required?: string[]; /** Object openness for `type: "object"`. */ additionalProperties?: boolean; /** Item schema for `type: "array"`; absent accepts any JSON item. */ items?: DshJsonSchema; /** Exact-one union; ≥2 branches. */ oneOf?: DshJsonSchema[]; /** Allowed scalar values. */ enum?: Array; /** Single allowed scalar value. */ const?: string | number | boolean | null; /** Annotation keywords (non-validating). */ description?: string; title?: string; default?: unknown; examples?: unknown; } /** * Loose mirror of `ToolRunContext` — the second argument of `defineTool`'s * `execute`. Only the fields this adapter reads are typed; the dsh host * supplies the real object at runtime. rc.6 declares `callId`, * `deferContext`, and `concludeTurn` REQUIRED on the real `ToolRunContext` * (`dsh-tools/lib/types/index.d.ts:197,290,299`), so they are required here. */ export interface DshToolRunContext { /** REQUIRED caller-owned cancellation (contract §3.5). */ signal: AbortSignal; /** REQUIRED provider-issued call id (`index.d.ts:197`). */ callId: string; rootCallId?: string; /** The agent on whose behalf the call runs (scope routing key). */ agent?: { id?: string; session?: { id?: string; header?: { cwd?: string; }; }; }; /** REQUIRED — defer context onto this call's result (`index.d.ts:290`). */ deferContext(context: unknown): void; /** REQUIRED — mark the result terminal for the agent turn (`index.d.ts:299`). */ concludeTurn(): void; } /** * Loose mirror of the dsh `ToolCallKind` vocabulary * (`packages/core/tools/src/presentation.ts:15`) — the icon/treatment category * a pure `presentCall` projection may declare. `other` is the client default. */ export type DshToolCallKind = "read" | "edit" | "delete" | "move" | "search" | "execute" | "fetch" | "other"; /** * Loose mirror of the dsh `GenericCallView` (`presentation.ts:53-75`): the * default pending-call card. Only the fields rolebox projects are modelled; the * dsh host owns the full `ToolCallView` union (`generic | terminal | diff`) and * a UI bridge switches on `card`. */ export interface DshGenericCallView { card: "generic"; /** Always-visible short label describing THIS call. */ title: string; /** Icon/treatment category (client default: `other`). */ kind?: DshToolCallKind; /** Salient input for a detail view (not the full raw args object). */ rawInput?: unknown; /** Follow-along file locations (a read's path + optional 1-based line). */ locations?: Array<{ path: string; line?: number; }>; } /** The pending-call render intent a tool may declare (generic arm only). */ export type DshToolCallView = DshGenericCallView; /** * Loose mirror of the dsh `GenericResultView` (`presentation.ts:146-155`): the * completed-state card. Omitted fields keep the pending title and render the * raw result content. */ export interface DshGenericResultView { card: "generic"; /** Replacement title for the completed call. */ title?: string; /** UI-facing result content (harness ContentBlocks). */ content?: DshContentBlock[]; } /** The completed-state render intent a tool may declare (generic arm only). */ export type DshToolResultView = DshGenericResultView; /** * The completed outcome handed to `presentResult` — the subset of the dsh * `ToolResult` (`index.ts:283-295`) rolebox reads: whether the call failed and * the durable `meta` payload projected by `output.presentationMeta`. */ export interface DshPresentResult { /** The final model-facing content (or the rendered error text on failure). */ content: DshContentBlock[]; /** Whether the call failed. */ isError: boolean; /** The tool-private presentation payload projected by `output.presentationMeta`. */ meta?: unknown; } /** * The register-ready definition: what `ctx.tools.register()` stores — the * structural mirror of the harness `ToolDefinition` register input * (`dsh-tools/lib/types/index.d.ts:106-124`), NOT `defineTool()` options. * `parameters` is already standard JSON Schema (`DshJsonSchema`) because * `register()` does NOT compile it, and `output.schema` is raw JSON Schema * (`DshJsonSchema`) for the same reason. The dsh plugin layer calls * `ctx.tools.register(compiled)` directly. * * The presentation/execution members (`presentCall`, `presentResult`, * `output.presentationMeta`, `timeoutMs`, `isConcurrencySafe`) are all OPTIONAL * and are emitted only for tools where rolebox has truthful data — see * `DSH_TOOL_PRESENTATION`. Their absence is the honest dsh generic fallback, * never a placeholder. */ export interface DshToolDefinition { name: string; description: string; parameters: DshJsonSchema; output: { schema: DshJsonSchema; render(args: unknown, value: unknown): DshContentBlock[]; /** * Pure, replay-safe projection of the canonical ToolResult's own display * metadata into the durable `tool/result` `meta` payload * (`index.ts:210`). Computed only for top-level calls and MUST depend only * on `args` + `value`, so a session-log replay reconstructs identical meta. * Omitted when rolebox has no truthful display metadata for the tool. */ presentationMeta?(args: unknown, value: unknown): unknown; }; /** * Cooperative tool-call timeout budget in milliseconds (`index.ts:247`). * Omitted for every rolebox tool: the canonical contract carries no fixed, * tool-wide deadline, and the tools' per-call timeout arguments are not a * fixed cooperative budget — declaring one would kill legitimate long calls. */ timeoutMs?: number; /** * Pure synchronous classifier for overlap with sibling calls (`index.ts:261`). * Declared only for rolebox tools whose body provably reads and mutates no * parent-owned state (see `DSH_TOOL_PRESENTATION`); omitted elsewhere. */ isConcurrencySafe?(args: unknown): boolean; /** * Pending-state render intent, derived from `args` alone (`index.ts:271`). * Pure and side-effect-free — dsh may call it during live streaming AND a * session-log replay. Omitted when rolebox has no truthful call view. */ presentCall?(args: unknown): DshToolCallView | undefined; /** * Completed-state render intent, from `args` and the durable result * (`index.ts:279`). Pure and side-effect-free for the same replay reason. * Omitted when rolebox has no truthful result view. */ presentResult?(args: unknown, result: DshPresentResult): DshToolResultView | undefined; execute(args: Record, exec: DshToolRunContext): Promise; } /** * Compile the implicit parameter root (a per-property author-DSL map) to the * object-rooted raw JSON Schema `register()` stores. */ export declare function dslParameterMapToJsonSchema(spec: DshParameterSchemaSpec): DshJsonSchema; /** * The tool name dsh reserves unconditionally for its PTC (programmatic tool * calling) / code-mode presentation transport — dsh `RUN_CODE_NAME` * (`packages/core/tools/src/ptc.ts:20`, 0.1.5-rc.1). A rolebox tool can never * take this name: `ctx.tools.register()` throws on the collision. */ export declare const DSH_RESERVED_RUN_CODE_NAME = "run_code"; /** * Thrown by {@link DshToolFactory} at compile time when a tool would take the * dsh harness-reserved name `run_code`. dsh reserves it unconditionally for the * PTC / code-mode presentation transport and `ctx.tools.register()` throws on * the collision (dsh 0.1.5-rc.1 `packages/core/tools/src/index.ts:1044-1046`), * so registration cannot succeed. The factory refuses BEFORE registration with * this clear, named error instead of letting the host throw a bare `Error` * mid-registration. */ export declare class DshReservedToolNameError extends Error { /** The colliding tool name (the reserved transport name). */ readonly toolName: string; constructor(toolName: string); } /** One tool's optional native-presentation / execution-classifier surface. */ export interface DshToolPresentation { presentCall?(args: unknown): DshToolCallView | undefined; presentationMeta?(args: unknown, value: unknown): unknown; presentResult?(args: unknown, result: DshPresentResult): DshToolResultView | undefined; isConcurrencySafe?(args: unknown): boolean; } /** * The rolebox tools that render natively in the dsh client. Exported so tests * and the contract drift detector can assert the surface. Tools absent here * fall back to dsh's generic presentation — the honest fallback when rolebox * has no truthful view to declare. */ export declare const DSH_TOOL_PRESENTATION: Readonly>; /** * IToolFactory adapter for the dsh platform. * * Compiles CanonicalToolDefs into register-ready definitions * (`DshToolDefinition`, the structural mirror of the harness `ToolDefinition` * register input — NOT `defineTool()` options): zod args → standard JSON * Schema `parameters`, the raw annotation-only `{}` for `output.schema`, a * text render, and an execute that maps `exec.signal` → `context.abort` and * returns the canonical ToolResult. * * Prefer compileAll() over compile(): dsh tool definitions require a `name`, * which only the record key provides (same constraint as the Pi adapter). */ export declare class DshToolFactory implements IToolFactory { #private; compile(def: CanonicalToolDef): unknown; compileAll(defs: Record): Record; } //# sourceMappingURL=tool-factory.d.ts.map