@oh-my-ai-sdk/omakase 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -12,15 +12,16 @@ configured URI sources. No model-facing registry-management tools.
12
12
  ## CLI setup
13
13
 
14
14
  Requires Node.js 22.13 or newer. The host itself needs no model key; your calling
15
- Claude/Codex session supplies its own model. Registered child agents additionally
16
- need Bun, their bootstrap prerequisites, and their own configured credentials
17
- (see below). Configuration stays outside harness MCP registration.
15
+ Claude/Codex session supplies its own model. Registered child agents and decision
16
+ tools use their own configured credentials. Child agents additionally need Bun
17
+ and their bootstrap prerequisites (see below). Configuration stays outside
18
+ harness MCP registration.
18
19
 
19
- For a local beta candidate, install its packed `.tgz` with `bun add /absolute/path/to/archive.tgz`
20
+ For a local candidate, install its packed `.tgz` with `bun add /absolute/path/to/archive.tgz`
20
21
  and configure your MCP client with the absolute path to that installation's
21
- `node_modules/.bin/omakase`. The registry commands below require the `beta`
22
- dist-tag to have been published; building or installing a local candidate does
23
- not publish it.
22
+ `node_modules/.bin/omakase`. The registry commands below install the published
23
+ `latest` release on the `0.1.x` line. Building or installing a local candidate
24
+ does not publish it.
24
25
 
25
26
  Create `~/.config/omakase/host.config.mjs` (or
26
27
  `$XDG_CONFIG_HOME/omakase/host.config.mjs`):
@@ -321,6 +322,88 @@ The private OMP adapter is not bundled: OMP **as a child backend** requires its
321
322
  separately installed/built adapter; using an MCP-capable harness as the caller
322
323
  does not require that child adapter.
323
324
 
325
+ ## Decision controller in ordinary or durable cells
326
+
327
+ `createDecisionTool({ model, configurationId })` returns an ordinary AI SDK tool.
328
+ Register it as `tools.decision` in a custom host, or set the optional `decision`
329
+ configuration on `createSkillsHost` to add it alongside `tools.read`. The existing
330
+ MCP server still exposes one `cell` tool; no separate decision server is needed.
331
+
332
+ Example `host.config.mjs` (install the provider beside this config, not inside
333
+ Omakase):
334
+
335
+ ```js
336
+ import { createTypeSafeAi } from '@ai-sdk/typesafe-ai';
337
+
338
+ export default async ({ createSkillsHost }) => createSkillsHost({
339
+ collections: [],
340
+ cwd: '/absolute/path/to/workspace',
341
+ decision: {
342
+ model: createTypeSafeAi().decisionModel('jev-1.13.0'),
343
+ configurationId: 'review-controller-v1',
344
+ },
345
+ });
346
+ ```
347
+
348
+ This example uses `TYPESAFE_AI_API_KEY` and the tested
349
+ `@ai-sdk/typesafe-ai@3.0.13` adapter with Omakase's pinned `ai@7.0.128`.
350
+ The experimental decision protocol can change in patch releases: adapter
351
+ `3.0.17` expects a newer state representation and is not compatible with this
352
+ core version. No provider is installed, selected, or silently substituted by
353
+ Omakase. Other SDK decision-model instances may be supplied, but their provider
354
+ compatibility must be verified; this release does not include a Clef adapter.
355
+
356
+ Native custom hosts import `createDecisionTool` from `@oh-my-ai-sdk/omakase` and
357
+ use `await host.register('tools', { decision: createDecisionTool(options) })`.
358
+ Include other `tools` leaves in that same registration: namespaces cannot be
359
+ registered twice. The CLI also supplies `createDecisionTool` to its host factory.
360
+
361
+ Submit this body through the existing `cell` API:
362
+
363
+ ```ts
364
+ const { answers } = await tools.decision({
365
+ state: { reviews: ["No blocking defects"], tests: "passed" },
366
+ questions: {
367
+ blocked: { type: "boolean", instructions: "Is there a blocking defect?" },
368
+ next: {
369
+ type: "choice",
370
+ instructions: "Choose the next action.",
371
+ criteria: { fix: "Repair a defect", finish: "Verified completion" },
372
+ },
373
+ quality: {
374
+ type: "score",
375
+ instructions: "Rate completeness.",
376
+ criteria: ["Incomplete", "Partly complete", "Complete"],
377
+ },
378
+ },
379
+ });
380
+ const { blocked, next, quality } = answers;
381
+ if (!blocked || !next || !quality ||
382
+ blocked.type !== "boolean" || next.type !== "choice" || quality.type !== "score")
383
+ throw new Error("Unexpected decision answer types");
384
+ return { blocked: blocked.probability, next: next.choice, quality: quality.score };
385
+ ```
386
+
387
+ - State and instructions accept a JSON string, object or array. Question names
388
+ identify answers. Criteria descriptions can additionally be `null`.
389
+ - `boolean` returns P(true), not a Boolean flag. `choice` returns an option name.
390
+ `score` returns a fractional position from zero through the last rubric index.
391
+ Choice/score distributions are optional; calibration is not portable across
392
+ providers. The result contains `{ answers, modelId }`, not SDK transport data.
393
+ - JavaScript owns branching. A judgment never grants tool permissions or bypasses
394
+ native approval. Malformed answers and provider errors fail the tool call;
395
+ no fallback judgment or automatic inference retry is supplied.
396
+ - Ordinary cells execute directly. Durable cells record each decision invocation;
397
+ all questions in one call share that checkpoint. Separate calls can progress
398
+ independently. Recorded answers are reused; an interrupted inference without a
399
+ committed receipt fails `OUTCOME_UNKNOWN` rather than silently re-inferencing.
400
+ - The configured provider/model and `configurationId` contribute to durable
401
+ catalog identity via `metadata.omakase.executionIdentity`. Change the revision
402
+ whenever endpoint, adapter or hidden settings change, and keep model instances
403
+ immutable. Pending runs reject configuration drift with `RECOVERY_BLOCKED`;
404
+ retain the admitted configuration to recover them. Do not use secrets in this
405
+ revision or a moving model alias when reproducibility matters.
406
+
324
407
  ## Skill reads through Code Mode
325
408
 
326
409
  Call `cell({})` to inspect the current full leaf catalog. Submit the following
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-11T13:32:12.517Z",
2
+ "generatedAt": "2026-10-11T16:56:28.358Z",
3
3
  "entry": "worker.mjs",
4
4
  "runtime": "bun >=1.3.14 (launched per run by the host; stdin/stdout JSON wire)",
5
5
  "adapters": {
@@ -1,8 +1,8 @@
1
1
  {
2
- "generatedAt": "2026-10-11T13:32:12.517Z",
2
+ "generatedAt": "2026-10-11T16:56:28.358Z",
3
3
  "source": "resolved package manifests walked from esbuild metafile inputs (authoritative, not pin literals)",
4
4
  "packages": {
5
- "@oh-my-ai-sdk/omakase": "0.1.1",
5
+ "@oh-my-ai-sdk/omakase": "0.1.2",
6
6
  "@ai-sdk/provider": "4.0.7",
7
7
  "eventsource-parser": "3.1.1",
8
8
  "@workflow/serde": "4.1.0",
@@ -9,7 +9,7 @@ export type CellGuestContract = Readonly<{
9
9
  declarations: string;
10
10
  /** Sorted tool names of the snapshot, frozen with the fingerprint. */
11
11
  names: readonly string[];
12
- /** Stable digest of the exact admitted catalog (names and schemas). */
12
+ /** Stable digest of the admitted catalog, including declared execution identities. */
13
13
  fingerprint: string;
14
14
  }>;
15
15
  /** Project one frozen host snapshot into the guest TypeScript contract. */
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { asSchema } from 'ai';
3
3
  import { isRecord } from './type-guard.js';
4
+ import { toolExecutionIdentity } from './execution-policy.js';
4
5
  /**
5
6
  * Ambient TypeScript contract for one exact tool catalog snapshot plus the
6
7
  * guest bindings both cell engines really provide. The semantic preflight and
@@ -63,7 +64,7 @@ export async function projectCellGuestContract(tools) {
63
64
  const leaf = tools[name];
64
65
  const input = await disclosedSchema(leaf.inputSchema);
65
66
  const output = leaf.outputSchema === undefined ? undefined : await disclosedSchema(leaf.outputSchema);
66
- projected.push({ name, input, output, signature: `(input: ${schemaToType(input)}) => Promise<${output === undefined ? 'unknown' : schemaToType(output)}>` });
67
+ projected.push({ name, input, output, executionIdentity: toolExecutionIdentity(leaf), signature: `(input: ${schemaToType(input)}) => Promise<${output === undefined ? 'unknown' : schemaToType(output)}>` });
67
68
  }
68
69
  const canonicalNames = new Set(projected.map(entry => entry.name));
69
70
  const fields = projected.flatMap(entry => {
@@ -96,7 +97,11 @@ export async function projectCellGuestContract(tools) {
96
97
  '}>;',
97
98
  ].join('\n'));
98
99
  const declarations = [CONSOLE_DECLARATION, PARALLEL_DECLARATIONS, ...toolEntries, ...namespaceEntries].join('\n');
99
- const fingerprint = createHash('sha256').update(JSON.stringify(projected.map(entry => [entry.name, entry.input, entry.output ?? null]))).digest('hex');
100
+ // Preserve existing fingerprints when no implementation identity was declared.
101
+ const fingerprint = createHash('sha256').update(JSON.stringify(projected.map(entry => [
102
+ entry.name, entry.input, entry.output ?? null,
103
+ ...(entry.executionIdentity === undefined ? [] : [entry.executionIdentity]),
104
+ ]))).digest('hex');
100
105
  return Object.freeze({ declarations, names: Object.freeze(projected.map(entry => entry.name)), fingerprint });
101
106
  }
102
107
  // JSON Schema to TypeScript projection adapted from packages/bun-workbench
package/dist/cli.js CHANGED
@@ -11,6 +11,7 @@ import { createToolHost } from './host.js';
11
11
  import { mountMcpConfigFile } from './mcp-config.js';
12
12
  import { createCellFrontend } from './frontend.js';
13
13
  import { createLazyDurableCellExecutor } from './cell.js';
14
+ import { createDecisionTool } from './decision.js';
14
15
  let host;
15
16
  let server;
16
17
  let durable;
@@ -107,7 +108,7 @@ async function main() {
107
108
  throw new Error('Config must export a default async host factory');
108
109
  const { createSkillsHost } = await import('./skills-host.js');
109
110
  const { createAgentHost, mountAgents } = await import('./agents.js');
110
- host = await config.default(Object.freeze({ ...context, createSkillsHost, createAgentHost, mountAgents }));
111
+ host = await config.default(Object.freeze({ ...context, createSkillsHost, createAgentHost, mountAgents, createDecisionTool }));
111
112
  }
112
113
  if (interrupted) {
113
114
  stopping = undefined;
@@ -0,0 +1,28 @@
1
+ import { type Experimental_DecisionAnswer, type Experimental_DecisionQuestion, type Experimental_DecisionModel, type JSONValue, type Tool } from 'ai';
2
+ /** Model and revision are operator authority; guests only supply state and questions. */
3
+ export type DecisionToolOptions = {
4
+ /** Decision model instance bound at construction. Model id strings are rejected:
5
+ * they would silently resolve through the ambient default provider or gateway. */
6
+ readonly model: Exclude<Experimental_DecisionModel, string>;
7
+ /** Operator revision token distinguishing endpoint or settings revisions of the
8
+ * same provider and model. Never a credential; never guest-supplied. */
9
+ readonly configurationId: string;
10
+ };
11
+ /** Guest-visible input: one shared state plus named questions of any supported type. */
12
+ export type DecisionInput = {
13
+ state: DecisionState;
14
+ questions: Record<string, Experimental_DecisionQuestion>;
15
+ };
16
+ /** JSON-only result: answers keyed by question name plus the answering model id.
17
+ * Never the full SDK decision result, whose response carries a Date. */
18
+ export type DecisionOutput = {
19
+ answers: Record<string, Experimental_DecisionAnswer<Experimental_DecisionQuestion>>;
20
+ modelId: string;
21
+ };
22
+ /** SDK decision input contract: shared state must be a JSON string, object, or array. */
23
+ type DecisionState = string | Readonly<Record<string, JSONValue>> | readonly JSONValue[];
24
+ /** Decision tool over the SDK decide operation. The model instance, invocation
25
+ * options (maxRetries 0, caller-supplied abort signal), and identity hash are
26
+ * pinned at construction; guest input can never select or reconfigure them. */
27
+ export declare function createDecisionTool(options: DecisionToolOptions): Tool<DecisionInput, DecisionOutput>;
28
+ export {};
@@ -0,0 +1,193 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { experimental_decide, jsonSchema, tool, } from 'ai';
3
+ import { assertJson, prepareSchema } from './schema.js';
4
+ import { isRecord } from './type-guard.js';
5
+ /** Bump whenever decision tool invocation semantics change; hashed into execution identity. */
6
+ const IDENTITY_VERSION = 'omakase-decision-tool/v1';
7
+ const instructionsSchema = {
8
+ description: 'What to judge: a JSON string, object, or array.',
9
+ anyOf: [{ type: 'string' }, { type: 'object' }, { type: 'array' }],
10
+ };
11
+ const descriptionSchema = {
12
+ description: 'Option or level description: null or a JSON string, object, or array.',
13
+ anyOf: [{ type: 'string' }, { type: 'object' }, { type: 'array' }, { type: 'null' }],
14
+ };
15
+ const questionSchema = {
16
+ anyOf: [
17
+ {
18
+ type: 'object',
19
+ description: 'boolean question: answered with P(true).',
20
+ properties: {
21
+ type: { const: 'boolean' },
22
+ instructions: instructionsSchema,
23
+ criteria: {
24
+ type: 'object',
25
+ description: 'Optional outcome descriptions; only the keys "true" and "false" are allowed.',
26
+ properties: { true: descriptionSchema, false: descriptionSchema },
27
+ additionalProperties: false,
28
+ },
29
+ },
30
+ required: ['type', 'instructions'],
31
+ additionalProperties: false,
32
+ },
33
+ {
34
+ type: 'object',
35
+ description: 'choice question: answered with exactly one named option.',
36
+ properties: {
37
+ type: { const: 'choice' },
38
+ instructions: instructionsSchema,
39
+ criteria: {
40
+ type: 'object',
41
+ description: 'Nonempty map of option name to description; the answer names one option.',
42
+ minProperties: 1,
43
+ additionalProperties: descriptionSchema,
44
+ },
45
+ },
46
+ required: ['type', 'instructions', 'criteria'],
47
+ additionalProperties: false,
48
+ },
49
+ {
50
+ type: 'object',
51
+ description: 'score question: answered with a position on an ordered rubric.',
52
+ properties: {
53
+ type: { const: 'score' },
54
+ instructions: instructionsSchema,
55
+ criteria: {
56
+ type: 'array',
57
+ description: 'Ordered rubric levels indexed from zero; at least two levels.',
58
+ minItems: 2,
59
+ items: descriptionSchema,
60
+ },
61
+ },
62
+ required: ['type', 'instructions', 'criteria'],
63
+ additionalProperties: false,
64
+ },
65
+ ],
66
+ };
67
+ const decisionInputContract = {
68
+ type: 'object',
69
+ description: 'Ask the operator-configured decision model questions about one shared state. Model, provider, and endpoint selection is host authority and never accepted here.',
70
+ properties: {
71
+ state: {
72
+ description: 'The single shared state every question is judged against: a JSON string, object, or array.',
73
+ anyOf: [{ type: 'string' }, { type: 'object' }, { type: 'array' }],
74
+ },
75
+ questions: {
76
+ type: 'object',
77
+ description: 'Nonempty map of question name to question; exactly one typed answer is returned per name.',
78
+ minProperties: 1,
79
+ additionalProperties: questionSchema,
80
+ },
81
+ },
82
+ required: ['state', 'questions'],
83
+ additionalProperties: false,
84
+ };
85
+ const answerSchema = {
86
+ description: 'One typed answer; the type field narrows the payload.',
87
+ anyOf: [
88
+ {
89
+ type: 'object',
90
+ description: 'Boolean answer: probability is P(true).',
91
+ properties: {
92
+ type: { const: 'boolean' },
93
+ probability: { type: 'number', minimum: 0, maximum: 1 },
94
+ },
95
+ required: ['type', 'probability'],
96
+ },
97
+ {
98
+ type: 'object',
99
+ description: 'Choice answer: the selected option name, plus an optional distribution over the options.',
100
+ properties: {
101
+ type: { const: 'choice' },
102
+ choice: { type: 'string' },
103
+ probabilities: { type: 'object', additionalProperties: { type: 'number', minimum: 0, maximum: 1 } },
104
+ },
105
+ required: ['type', 'choice'],
106
+ },
107
+ {
108
+ type: 'object',
109
+ description: 'Score answer: the rubric position, plus an optional distribution over level indexes.',
110
+ properties: {
111
+ type: { const: 'score' },
112
+ score: { type: 'number', minimum: 0 },
113
+ probabilities: { type: 'object', additionalProperties: { type: 'number', minimum: 0, maximum: 1 } },
114
+ },
115
+ required: ['type', 'score'],
116
+ },
117
+ ],
118
+ };
119
+ const decisionOutputContract = {
120
+ type: 'object',
121
+ description: 'Answers keyed by question name plus the answering model id. JSON only.',
122
+ properties: {
123
+ answers: {
124
+ type: 'object',
125
+ description: 'Exactly one answer per requested question name.',
126
+ minProperties: 1,
127
+ additionalProperties: answerSchema,
128
+ },
129
+ modelId: { type: 'string', minLength: 1, description: 'The decision model that produced the answers.' },
130
+ },
131
+ required: ['answers', 'modelId'],
132
+ additionalProperties: false,
133
+ };
134
+ function deepFreeze(value) {
135
+ for (const child of Object.values(value))
136
+ if (child && typeof child === 'object')
137
+ deepFreeze(child);
138
+ Object.freeze(value);
139
+ }
140
+ deepFreeze(decisionInputContract);
141
+ deepFreeze(decisionOutputContract);
142
+ const decisionInputSchema = jsonSchema(decisionInputContract);
143
+ const decisionOutputSchema = jsonSchema(decisionOutputContract);
144
+ // The host and direct native invocation share the same boundary validator.
145
+ // Prepare lazily once; do not duplicate the SDK's question/answer semantics.
146
+ let preparedInput;
147
+ /** Projects SDK-validated answers onto the JSON output contract: optional
148
+ * probabilities fields are emitted only when the provider defined them, so
149
+ * answers carrying explicit undefined values stay JSON-valid. Required fields
150
+ * pass through verbatim; the SDK has already validated their semantics. */
151
+ function projectDecisionAnswers(answers) {
152
+ return Object.fromEntries(Object.entries(answers).map(([id, answer]) => {
153
+ if (answer.type === 'choice')
154
+ return [id, { type: 'choice', choice: answer.choice, ...(answer.probabilities === undefined ? {} : { probabilities: answer.probabilities }) }];
155
+ if (answer.type === 'score')
156
+ return [id, { type: 'score', score: answer.score, ...(answer.probabilities === undefined ? {} : { probabilities: answer.probabilities }) }];
157
+ return [id, { type: 'boolean', probability: answer.probability }];
158
+ }));
159
+ }
160
+ /** Decision tool over the SDK decide operation. The model instance, invocation
161
+ * options (maxRetries 0, caller-supplied abort signal), and identity hash are
162
+ * pinned at construction; guest input can never select or reconfigure them. */
163
+ export function createDecisionTool(options) {
164
+ if (!isRecord(options))
165
+ throw new Error('Decision tool options must be an object');
166
+ if (typeof options.configurationId !== 'string' || options.configurationId.trim() === '')
167
+ throw new Error('Decision tool configurationId must be a nonblank string');
168
+ const model = options.model;
169
+ if (!model || typeof model !== 'object' || typeof model.provider !== 'string' || !model.provider.trim() || typeof model.modelId !== 'string' || !model.modelId.trim())
170
+ throw new Error('Decision tool requires a configured decision model instance');
171
+ const provider = model.provider;
172
+ const modelId = model.modelId;
173
+ const executionIdentity = createHash('sha256').update(JSON.stringify([IDENTITY_VERSION, provider, modelId, options.configurationId])).digest('hex');
174
+ const metadata = { omakase: { replay: 'safe', executionIdentity } };
175
+ Object.freeze(metadata.omakase);
176
+ Object.freeze(metadata);
177
+ return tool({
178
+ description: 'Answer typed judgment questions about one shared state with the operator-configured decision model. Ask several named questions in a single call: boolean questions return P(true), choice questions select exactly one named option, score questions place the state on an ordered rubric indexed from zero. The decision model, provider, and endpoint are fixed by the host operator and cannot be selected or reconfigured through input. Returns { answers, modelId } with exactly one typed answer per question name.',
179
+ inputSchema: decisionInputSchema,
180
+ outputSchema: decisionOutputSchema,
181
+ metadata,
182
+ execute: async (input, execution) => {
183
+ if (model.provider !== provider || model.modelId !== modelId)
184
+ throw new Error('Decision model provider or modelId changed after decision tool construction; recreate the tool to bind the new identity');
185
+ const prepared = await (preparedInput ??= prepareSchema(decisionInputSchema));
186
+ await prepared.validate(input);
187
+ const decision = await experimental_decide({ model, state: input.state, questions: input.questions, maxRetries: 0, abortSignal: execution.abortSignal });
188
+ const answers = projectDecisionAnswers(decision.answers);
189
+ assertJson(answers);
190
+ return { answers, modelId: decision.response.modelId };
191
+ },
192
+ });
193
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-11T13:32:07.846Z",
2
+ "generatedAt": "2026-10-11T16:56:22.262Z",
3
3
  "source": "resolved package manifests from the isolated scratch graph (authoritative, not pin literals)",
4
4
  "packages": {
5
5
  "@workflow/utils": "4.1.4",
@@ -5,3 +5,7 @@ export declare function toolReplayPolicy(tool: {
5
5
  }): ToolReplayPolicy | undefined;
6
6
  /** Unknown executors fail closed for an explicitly unsafe implementation. */
7
7
  export declare function assertToolReplayPolicy(policy: ToolReplayPolicy | undefined, replays: boolean | undefined, name: string): void;
8
+ /** Optional trusted configuration revision, included in durable admission. */
9
+ export declare function toolExecutionIdentity(tool: {
10
+ metadata?: unknown;
11
+ }): string | undefined;
@@ -19,3 +19,20 @@ export function assertToolReplayPolicy(policy, replays, name) {
19
19
  if (policy === 'unsafe' && replays !== false)
20
20
  throw new Error(`Replay-unsafe tool cannot use a replaying or unspecified executor: ${name}`);
21
21
  }
22
+ /** Optional trusted configuration revision, included in durable admission. */
23
+ export function toolExecutionIdentity(tool) {
24
+ const metadata = tool.metadata;
25
+ if (!metadata || typeof metadata !== 'object')
26
+ return undefined;
27
+ const omakase = metadata.omakase;
28
+ if (omakase === undefined)
29
+ return undefined;
30
+ if (!omakase || typeof omakase !== 'object' || Array.isArray(omakase))
31
+ throw new Error('Invalid omakase execution metadata');
32
+ const identity = omakase.executionIdentity;
33
+ if (identity === undefined)
34
+ return undefined;
35
+ if (typeof identity !== 'string' || !identity.trim())
36
+ throw new Error('Tool execution identity must be a nonempty string');
37
+ return identity;
38
+ }
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export { createToolHostFrontend, createCellFrontend, HOST_FRONTEND_PREAMBLE, CEL
11
11
  export { createSkillLibrary, mountSkills, type SkillLibrary, type SkillEntry, type SkillRead } from './skills.js';
12
12
  export { createReadTool, createMcpReadResolver, mcpResourceAddress, type ReadResult, type ReadResolver, type ReadOptions } from './read.js';
13
13
  export { toolReplayPolicy, assertToolReplayPolicy, type ToolReplayPolicy } from './execution-policy.js';
14
+ export { createDecisionTool, type DecisionToolOptions, type DecisionInput, type DecisionOutput } from './decision.js';
package/dist/index.js CHANGED
@@ -9,3 +9,4 @@ export { createToolHostFrontend, createCellFrontend, HOST_FRONTEND_PREAMBLE, CEL
9
9
  export { createSkillLibrary, mountSkills } from './skills.js';
10
10
  export { createReadTool, createMcpReadResolver, mcpResourceAddress } from './read.js';
11
11
  export { toolReplayPolicy, assertToolReplayPolicy } from './execution-policy.js';
12
+ export { createDecisionTool } from './decision.js';
@@ -1,4 +1,5 @@
1
1
  import { type SkillCollection, type SkillsPeer } from './skills-server.js';
2
+ import { type DecisionToolOptions } from './decision.js';
2
3
  /** Ready-to-run local host. The skills service owns collections and discovery;
3
4
  * the host imports its single list tool through the ordinary MCP adapter.
4
5
  */
@@ -6,4 +7,6 @@ export declare function createSkillsHost(options: {
6
7
  collections: readonly SkillCollection[];
7
8
  peers?: readonly SkillsPeer[];
8
9
  cwd?: string;
10
+ /** Optional host-owned decision model, exposed alongside tools.read. */
11
+ decision?: DecisionToolOptions;
9
12
  }): Promise<import("./host.js").ToolHost>;
@@ -3,6 +3,7 @@ import { createToolHost } from './host.js';
3
3
  import { createSkillsServer } from './skills-server.js';
4
4
  import { mountMcpSdk } from './mcp-sdk-source.js';
5
5
  import { createReadTool, createMcpReadResolver, mcpResourceAddress } from './read.js';
6
+ import { createDecisionTool } from './decision.js';
6
7
  /** Ready-to-run local host. The skills service owns collections and discovery;
7
8
  * the host imports its single list tool through the ordinary MCP adapter.
8
9
  */
@@ -28,7 +29,10 @@ export async function createSkillsHost(options) {
28
29
  return { ...result, structuredContent: { entries: scoped }, content: [{ type: 'text', text: JSON.stringify(scoped) }] };
29
30
  },
30
31
  });
31
- await host.register('tools', { read: createReadTool({ cwd: options.cwd, resolvers: { mcp: createMcpReadResolver({ skills: source }) } }) }, {
32
+ await host.register('tools', {
33
+ read: createReadTool({ cwd: options.cwd, resolvers: { mcp: createMcpReadResolver({ skills: source }) } }),
34
+ ...(options.decision ? { decision: createDecisionTool(options.decision) } : {}),
35
+ }, {
32
36
  dispose: async () => { if (timer)
33
37
  clearInterval(timer); await service.close(); },
34
38
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-ai-sdk/omakase",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "One Code Mode tool host for native AI SDK agents and MCP clients, with a stateful skills service",
6
6
  "exports": {