|
| 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