Skip to content

Commit cea6c1e

Browse files
heskewclaude
andcommitted
feat(models): add public type surface for scope.models
Part of #628 (Phase 1 of #510). Defines the TypeScript interfaces that every model-backend phase consumes: - Models facade interface (embed, generate, generateStream) - ModelBackend interface with optional methods + ModelCapabilities - EmbedOpts, GenerateOpts, GenerateInput - BackendOpts<T>: caller opts intersected with AccountingContext - AccountingContext (tenantId, app) - ModelCallResult<T> discriminated union — 'completed' is unwrapped by the facade; 'pending' reserved for future LRO backends - TokenUsage - Message, ToolDef, ToolCall, GenerateResult, GenerateChunk Shapes pulled from the canonical planning artifact at tmp/harper-510-phase-1-detail.md. Public Models interface omits getOperation/cancelOperation per #628 issue body (out of scope for Phase 1; lands with the first LRO-capable backend). No runtime code yet — this file is consumed by Models.ts, backendRegistry.ts, TestBackend.ts, and analyticsTable.ts in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6015b28 commit cea6c1e

1 file changed

Lines changed: 124 additions & 0 deletions

File tree

resources/models/types.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Public type surface for the model-access API (`scope.models`).
3+
*
4+
* Phase 1 foundation of #510. Other phases (ollama, openai, gateway, @embed,
5+
* anthropic, bedrock) consume these types; changes here cascade.
6+
*
7+
* Reference: planning artifact `tmp/harper-510-phase-1-detail.md` (canonical shapes).
8+
*/
9+
10+
export interface Models {
11+
embed(input: string | string[], opts?: EmbedOpts): Promise<Float32Array[]>;
12+
generate(input: GenerateInput, opts?: GenerateOpts): Promise<GenerateResult>;
13+
generateStream(input: GenerateInput, opts?: GenerateOpts): AsyncIterable<GenerateChunk>;
14+
}
15+
16+
export interface ModelBackend {
17+
readonly name: string;
18+
capabilities(): ModelCapabilities;
19+
embed?(input: string | string[], opts: BackendOpts<EmbedOpts>): Promise<ModelCallResult<Float32Array[]>>;
20+
generate?(input: GenerateInput, opts: BackendOpts<GenerateOpts>): Promise<ModelCallResult<GenerateResult>>;
21+
generateStream?(input: GenerateInput, opts: BackendOpts<GenerateOpts>): AsyncIterable<GenerateChunk>;
22+
}
23+
24+
export interface ModelCapabilities {
25+
embed: boolean;
26+
generate: boolean;
27+
stream: boolean;
28+
tools: boolean;
29+
adapters: boolean;
30+
}
31+
32+
export type EmbedOpts = {
33+
model?: string;
34+
/** For models that distinguish document-vs-query embeddings (e.g. nomic-embed-text); ignored otherwise. */
35+
inputType?: 'document' | 'query';
36+
signal?: AbortSignal;
37+
};
38+
39+
export type GenerateOpts = {
40+
model?: string;
41+
adapter?: string;
42+
temperature?: number;
43+
maxTokens?: number;
44+
responseFormat?: 'text' | 'json' | { schema: object };
45+
tools?: ToolDef[];
46+
toolMode?: 'return' | 'auto';
47+
/** Accepted in Phase 1; activates with #511 (ConversationResource). */
48+
conversationId?: string;
49+
signal?: AbortSignal;
50+
};
51+
52+
export type GenerateInput = string | Message[] | { messages: Message[]; tools?: ToolDef[]; system?: string };
53+
54+
/** Options handed to a backend: caller-supplied opts plus runtime accounting context. */
55+
export type BackendOpts<TOpts> = TOpts & { accounting: AccountingContext };
56+
57+
export interface AccountingContext {
58+
/** Free-form tenant identifier (v1). Pending canonical model from #510 comment thread. */
59+
tenantId?: string;
60+
/** Resource path (e.g. matched route) of the calling Resource, if any. */
61+
app?: string;
62+
}
63+
64+
/**
65+
* Backend call result.
66+
*
67+
* `pending` is reserved for future long-running operations (Bedrock batch, fabric LROs);
68+
* no Phase 1 backend emits it. The public `Models` facade unwraps `completed` and
69+
* matches #510's `Promise<Float32Array[]>` / `Promise<GenerateResult>` signatures,
70+
* throwing if a backend returns `pending` until the LRO surface is added.
71+
*/
72+
export type ModelCallResult<T> =
73+
| { status: 'completed'; output: T; usage?: TokenUsage }
74+
| { status: 'pending'; operationId: string; resumeAfter?: number };
75+
76+
export interface TokenUsage {
77+
promptTokens?: number;
78+
completionTokens?: number;
79+
embeddingTokens?: number;
80+
gpuMs?: number;
81+
latencyMs?: number;
82+
}
83+
84+
export interface Message {
85+
role: 'system' | 'user' | 'assistant' | 'tool';
86+
content: string;
87+
/** When `role === 'assistant'`: tool calls the model requested. */
88+
toolCalls?: ToolCall[];
89+
/** When `role === 'tool'`: id of the tool call this message responds to. */
90+
toolCallId?: string;
91+
}
92+
93+
export interface ToolDef {
94+
name: string;
95+
description: string;
96+
/** JSON Schema for the tool's input. */
97+
parameters: object;
98+
}
99+
100+
export interface ToolCall {
101+
id: string;
102+
name: string;
103+
/** Parsed tool input. Backends that deliver stringified JSON (OpenAI) normalize before yielding. */
104+
arguments: object;
105+
}
106+
107+
export interface GenerateResult {
108+
content: string;
109+
toolCalls?: ToolCall[];
110+
/** Why generation stopped. Backend-agnostic; backends map their native reasons. */
111+
finishReason: 'stop' | 'length' | 'tool_calls' | 'content_filter';
112+
}
113+
114+
export interface GenerateChunk {
115+
/** Incremental text appended since the previous chunk. */
116+
deltaContent?: string;
117+
/**
118+
* Tool-call deltas accumulating across chunks. A streaming backend may deliver
119+
* the same tool-call id multiple times with partial fields as it builds up.
120+
*/
121+
deltaToolCalls?: Partial<ToolCall>[];
122+
/** Set on the final chunk. */
123+
finishReason?: GenerateResult['finishReason'];
124+
}

0 commit comments

Comments
 (0)