dsh-magpie-connect 0.2.0
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/LICENSE +21 -0
- package/README.md +133 -0
- package/cordis.patch.yml +23 -0
- package/dsh.plugin.json +9 -0
- package/lib/client.js +744 -0
- package/lib/index.js +1638 -0
- package/lib/index.js.map +7 -0
- package/lib/types/adapter/catalog.d.ts +189 -0
- package/lib/types/adapter/events.d.ts +164 -0
- package/lib/types/adapter/magpie-adapter.d.ts +173 -0
- package/lib/types/adapter/messages.d.ts +196 -0
- package/lib/types/adapter/watchdog.d.ts +27 -0
- package/lib/types/client/i18n.d.ts +15 -0
- package/lib/types/client/index.d.ts +31 -0
- package/lib/types/client/model-list.d.ts +53 -0
- package/lib/types/client/model-visibility.d.ts +65 -0
- package/lib/types/client/nav-icon.d.ts +45 -0
- package/lib/types/client/page.d.ts +6 -0
- package/lib/types/client/write-queue.d.ts +25 -0
- package/lib/types/config.d.ts +66 -0
- package/lib/types/index.d.ts +88 -0
- package/lib/types/settings.d.ts +59 -0
- package/package.json +81 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-ai AssistantMessageEvent -> harness StreamChunks.
|
|
3
|
+
* The chunk stream must end with `usage` then `finish`.
|
|
4
|
+
*/
|
|
5
|
+
export type HarnessChunk = {
|
|
6
|
+
type: 'block-start';
|
|
7
|
+
index: number;
|
|
8
|
+
blockType: 'text' | 'reasoning' | 'tool-call';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'text-delta';
|
|
11
|
+
index: number;
|
|
12
|
+
text: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'block-end';
|
|
15
|
+
index: number;
|
|
16
|
+
block: {
|
|
17
|
+
type: 'text';
|
|
18
|
+
text: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'reasoning';
|
|
21
|
+
text: string;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'tool-call';
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
arguments: string;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
type: 'reasoning-delta';
|
|
30
|
+
index: number;
|
|
31
|
+
text: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: 'tool-call-delta';
|
|
34
|
+
index: number;
|
|
35
|
+
id: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
argumentsDelta: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'usage';
|
|
40
|
+
usage: {
|
|
41
|
+
inputTokens: number;
|
|
42
|
+
outputTokens: number;
|
|
43
|
+
cacheReadTokens?: number;
|
|
44
|
+
cacheWriteTokens?: number;
|
|
45
|
+
};
|
|
46
|
+
} | {
|
|
47
|
+
type: 'finish';
|
|
48
|
+
reason: FinishReason;
|
|
49
|
+
replayState?: unknown;
|
|
50
|
+
};
|
|
51
|
+
export type FinishReason = {
|
|
52
|
+
kind: 'stop';
|
|
53
|
+
} | {
|
|
54
|
+
kind: 'max-tokens';
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'tool-calls';
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'aborted';
|
|
59
|
+
failure: {
|
|
60
|
+
message: string;
|
|
61
|
+
code: string;
|
|
62
|
+
};
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'error';
|
|
65
|
+
failure: {
|
|
66
|
+
message: string;
|
|
67
|
+
code: string;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/** pi-ai AssistantMessageEvent vocabulary (subset we consume). */
|
|
71
|
+
export type PiEvent = {
|
|
72
|
+
type: 'start';
|
|
73
|
+
partial: PiAssistantPartial;
|
|
74
|
+
} | {
|
|
75
|
+
type: 'text_start';
|
|
76
|
+
contentIndex: number;
|
|
77
|
+
partial: PiAssistantPartial;
|
|
78
|
+
} | {
|
|
79
|
+
type: 'text_delta';
|
|
80
|
+
contentIndex: number;
|
|
81
|
+
delta: string;
|
|
82
|
+
partial: PiAssistantPartial;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'text_end';
|
|
85
|
+
contentIndex: number;
|
|
86
|
+
content: string;
|
|
87
|
+
partial: PiAssistantPartial;
|
|
88
|
+
} | {
|
|
89
|
+
type: 'thinking_start';
|
|
90
|
+
contentIndex: number;
|
|
91
|
+
partial: PiAssistantPartial;
|
|
92
|
+
} | {
|
|
93
|
+
type: 'thinking_delta';
|
|
94
|
+
contentIndex: number;
|
|
95
|
+
delta: string;
|
|
96
|
+
partial: PiAssistantPartial;
|
|
97
|
+
} | {
|
|
98
|
+
type: 'thinking_end';
|
|
99
|
+
contentIndex: number;
|
|
100
|
+
content: string;
|
|
101
|
+
partial: PiAssistantPartial;
|
|
102
|
+
} | {
|
|
103
|
+
type: 'toolcall_start';
|
|
104
|
+
contentIndex: number;
|
|
105
|
+
partial: PiAssistantPartial;
|
|
106
|
+
} | {
|
|
107
|
+
type: 'toolcall_delta';
|
|
108
|
+
contentIndex: number;
|
|
109
|
+
delta: string;
|
|
110
|
+
partial: PiAssistantPartial;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'toolcall_end';
|
|
113
|
+
contentIndex: number;
|
|
114
|
+
toolCall: {
|
|
115
|
+
id: string;
|
|
116
|
+
name: string;
|
|
117
|
+
arguments: Record<string, unknown>;
|
|
118
|
+
};
|
|
119
|
+
partial: PiAssistantPartial;
|
|
120
|
+
} | {
|
|
121
|
+
type: 'done';
|
|
122
|
+
message: PiDoneMessage;
|
|
123
|
+
} | {
|
|
124
|
+
type: 'error';
|
|
125
|
+
error: PiDoneMessage;
|
|
126
|
+
};
|
|
127
|
+
export interface PiAssistantPartial {
|
|
128
|
+
content: Array<{
|
|
129
|
+
type: string;
|
|
130
|
+
id?: string;
|
|
131
|
+
name?: string;
|
|
132
|
+
[key: string]: unknown;
|
|
133
|
+
}>;
|
|
134
|
+
[key: string]: unknown;
|
|
135
|
+
}
|
|
136
|
+
/** pi-ai AssistantMessage content block (done/error messages carry these). */
|
|
137
|
+
export type PiAssistantBlock = {
|
|
138
|
+
type: string;
|
|
139
|
+
[key: string]: unknown;
|
|
140
|
+
};
|
|
141
|
+
export interface PiDoneMessage {
|
|
142
|
+
api: string;
|
|
143
|
+
provider: string;
|
|
144
|
+
model: string;
|
|
145
|
+
responseModel?: string;
|
|
146
|
+
responseId?: string;
|
|
147
|
+
content: PiAssistantBlock[];
|
|
148
|
+
usage: {
|
|
149
|
+
input: number;
|
|
150
|
+
output: number;
|
|
151
|
+
cacheRead: number;
|
|
152
|
+
cacheWrite: number;
|
|
153
|
+
totalTokens: number;
|
|
154
|
+
};
|
|
155
|
+
stopReason: 'stop' | 'length' | 'toolUse' | 'error' | 'aborted';
|
|
156
|
+
errorMessage?: string;
|
|
157
|
+
[key: string]: unknown;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Translate one pi-ai event stream into harness chunks. pi-ai never throws
|
|
161
|
+
* mid-stream: failures arrive as `error` events and become error/aborted
|
|
162
|
+
* finish chunks.
|
|
163
|
+
*/
|
|
164
|
+
export declare function toStreamChunks(events: AsyncIterable<PiEvent>, contextWindow: number): AsyncGenerator<HarnessChunk>;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ModelCatalog } from './catalog.ts';
|
|
2
|
+
import { type HarnessChunk, type PiEvent } from './events.ts';
|
|
3
|
+
import { type AttachmentStore, type HarnessGenerateOptions } from './messages.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Magpie LAN gateway adapter: registers as a DSH LlmAdapter and streams
|
|
6
|
+
* directly from the configured API root (e.g. `…/v1`) — chat completions for most models plus the
|
|
7
|
+
* Responses API for responses-only models (Muse Spark / Codex / Grok lanes).
|
|
8
|
+
*
|
|
9
|
+
* Adapter contract: dsh-llm LlmAdapter (providerInfo/listModels/resolveModel/
|
|
10
|
+
* prepareCall/stream) — structural, no host import.
|
|
11
|
+
*
|
|
12
|
+
* Images resolve through the harness attachment service; thinking levels come
|
|
13
|
+
* from the gateway catalog and forward verbatim (including the `none`/`ultra`
|
|
14
|
+
* extensions pi-ai does not model).
|
|
15
|
+
*/
|
|
16
|
+
/** Structural pi-ai provider surface the adapter consumes. */
|
|
17
|
+
export interface PiProviderLike {
|
|
18
|
+
stream(model: unknown, context: unknown, options: unknown): AsyncIterable<PiEvent>;
|
|
19
|
+
streamSimple(model: unknown, context: unknown, options: unknown): AsyncIterable<PiEvent>;
|
|
20
|
+
}
|
|
21
|
+
/** Live endpoint read per request so the settings page applies without restart. */
|
|
22
|
+
export interface RuntimeEndpoint {
|
|
23
|
+
/** Versioned API root, e.g. `http://api.lan/v1` (no trailing slash). */
|
|
24
|
+
baseUrl(): string;
|
|
25
|
+
/** Bearer key sent to the gateway. */
|
|
26
|
+
apiKey(): string;
|
|
27
|
+
}
|
|
28
|
+
export declare const PROVIDER_ID = "dsh-magpie-connect";
|
|
29
|
+
/** Default picker label for the default route id. */
|
|
30
|
+
export declare const DEFAULT_DISPLAY_NAME = "magpie";
|
|
31
|
+
export interface MagpieModelInfo {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
contextWindow: number;
|
|
35
|
+
maxTokens: number;
|
|
36
|
+
}
|
|
37
|
+
export interface CatalogLike {
|
|
38
|
+
list(): string[];
|
|
39
|
+
decision(model: string): {
|
|
40
|
+
allowed: boolean;
|
|
41
|
+
source: string;
|
|
42
|
+
known: boolean;
|
|
43
|
+
};
|
|
44
|
+
getEntry?(model: string): {
|
|
45
|
+
nativeEndpoints: string[];
|
|
46
|
+
} | undefined;
|
|
47
|
+
reasoningFor?(model: string): {
|
|
48
|
+
efforts: Array<{
|
|
49
|
+
id: string;
|
|
50
|
+
name: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
}>;
|
|
53
|
+
defaultEffort: string;
|
|
54
|
+
} | undefined;
|
|
55
|
+
thinks?(model: string): boolean;
|
|
56
|
+
supportsImage?(model: string): boolean;
|
|
57
|
+
supportsEffort?(model: string, effort: string): boolean;
|
|
58
|
+
requiresResponsesApi?(model: string): boolean;
|
|
59
|
+
contextWindowFor?(model: string): number;
|
|
60
|
+
maxTokensFor?(model: string): number;
|
|
61
|
+
}
|
|
62
|
+
/** Connection-setup retries (429/5xx with backoff, interruptible by abort). */
|
|
63
|
+
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
64
|
+
/** Overall SDK request cap (replaces the OpenAI SDK 10 min default). */
|
|
65
|
+
export declare const DEFAULT_TIMEOUT_MS = 300000;
|
|
66
|
+
/** Max wait for the first upstream event (queueing happens here). */
|
|
67
|
+
export declare const DEFAULT_FIRST_EVENT_TIMEOUT_MS = 90000;
|
|
68
|
+
/** Max silence between upstream events once streaming. */
|
|
69
|
+
export declare const DEFAULT_IDLE_TIMEOUT_MS = 60000;
|
|
70
|
+
/**
|
|
71
|
+
* Plan the wire `reasoning_effort` for one request. Returns the effort to
|
|
72
|
+
* send via `provider.stream`, or undefined to omit the field (gateway
|
|
73
|
+
* default applies).
|
|
74
|
+
*
|
|
75
|
+
* - `off` aliases `none` (explicitly disable thinking).
|
|
76
|
+
* - `none` sends only when the model lists it; otherwise omitted.
|
|
77
|
+
* - `ultra` sends when listed; otherwise degrades to the highest listed
|
|
78
|
+
* strength level (`max` → … → `minimal`).
|
|
79
|
+
* - Known strength levels send verbatim when listed or when the model thinks
|
|
80
|
+
* without a ladder (gateway decides); otherwise clamped up-then-down to
|
|
81
|
+
* the nearest listed level so a stale remembered value never 400s.
|
|
82
|
+
* - Unknown values and non-reasoning models omit.
|
|
83
|
+
*/
|
|
84
|
+
export declare function planReasoningEffort(requested: unknown, model: string, catalog: Pick<CatalogLike, 'thinks' | 'supportsEffort' | 'reasoningFor'>): string | undefined;
|
|
85
|
+
export declare class MagpieAdapter {
|
|
86
|
+
#private;
|
|
87
|
+
constructor(catalog: CatalogLike, options?: {
|
|
88
|
+
magpieBaseUrl?: string;
|
|
89
|
+
baseUrl?: string;
|
|
90
|
+
providerOverride?: unknown;
|
|
91
|
+
/** Route id registered into DSH (model `provider` field + pi-ai provider tag). */
|
|
92
|
+
providerId?: string;
|
|
93
|
+
/** Display name reported via providerInfo (model picker grouping label). */
|
|
94
|
+
displayName?: string;
|
|
95
|
+
/** Gateway credential (LAN needs none). */
|
|
96
|
+
apiKey?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Live endpoint read per request (settings page). Falls back to the
|
|
99
|
+
* static `magpieBaseUrl`/`apiKey` options when absent.
|
|
100
|
+
*/
|
|
101
|
+
runtime?: RuntimeEndpoint;
|
|
102
|
+
/** Connection-setup retries for 429/5xx (default 2). */
|
|
103
|
+
maxRetries?: number;
|
|
104
|
+
/** Overall SDK request cap in ms (default 300000). */
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
/** Stall watchdog: max wait for the first upstream event in ms (default 90000; <=0 disables). */
|
|
107
|
+
firstEventTimeoutMs?: number;
|
|
108
|
+
/** Stall watchdog: max silence between upstream events in ms (default 60000; <=0 disables). */
|
|
109
|
+
idleTimeoutMs?: number;
|
|
110
|
+
/** Harness attachment service for image bytes (wired in index.ts). */
|
|
111
|
+
resolveAttachments?: () => AttachmentStore | undefined;
|
|
112
|
+
});
|
|
113
|
+
providerInfo(provider: string): {
|
|
114
|
+
id: string;
|
|
115
|
+
name: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* dsh-llm calls this unconditionally at registration.
|
|
119
|
+
* undefined = the host default retry policy.
|
|
120
|
+
*/
|
|
121
|
+
providerRetryPolicy(_provider: string): undefined;
|
|
122
|
+
/** Advisory catalog for the DSH model picker (deduped; dsh-llm rejects duplicates). */
|
|
123
|
+
listModels(provider: string): Array<{
|
|
124
|
+
provider: string;
|
|
125
|
+
id: string;
|
|
126
|
+
name: string;
|
|
127
|
+
inputModalities: string[];
|
|
128
|
+
}>;
|
|
129
|
+
resolveModel(provider: string, model: string): {
|
|
130
|
+
provider: string;
|
|
131
|
+
id: string;
|
|
132
|
+
name: string;
|
|
133
|
+
inputModalities: string[];
|
|
134
|
+
context: {
|
|
135
|
+
contextWindow: number;
|
|
136
|
+
};
|
|
137
|
+
defaultMaxTokens: number;
|
|
138
|
+
reasoning?: {
|
|
139
|
+
efforts: ReadonlyArray<{
|
|
140
|
+
id: string;
|
|
141
|
+
name: string;
|
|
142
|
+
description?: string;
|
|
143
|
+
}>;
|
|
144
|
+
defaultEffort?: string;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
prepareCall(provider: string, model: string, _signal?: AbortSignal): Promise<{
|
|
148
|
+
model: ReturnType<MagpieAdapter['resolveModel']>;
|
|
149
|
+
stream: (options: HarnessGenerateOptions) => AsyncGenerator<HarnessChunk>;
|
|
150
|
+
}>;
|
|
151
|
+
/**
|
|
152
|
+
* Stream one chat turn from the Magpie gateway: a single pi-ai stream,
|
|
153
|
+
* translated to harness chunks verbatim. Upstream failures (rate limit,
|
|
154
|
+
* auth, timeout, transport) arrive as classified finish reasons, and
|
|
155
|
+
* turn-level retries stay owned by DSH.
|
|
156
|
+
*
|
|
157
|
+
* A stall watchdog races every upstream event against a timer: on expiry
|
|
158
|
+
* the upstream is aborted and the turn ends fast with a TIMEOUT error
|
|
159
|
+
* instead of hanging to the SDK/harness timeout.
|
|
160
|
+
*/
|
|
161
|
+
stream(options: HarnessGenerateOptions): AsyncGenerator<HarnessChunk>;
|
|
162
|
+
/** Expose the live catalog snapshot for diagnostics. */
|
|
163
|
+
catalogStatus(): {
|
|
164
|
+
total: number;
|
|
165
|
+
exposed: number;
|
|
166
|
+
};
|
|
167
|
+
decisionFor(model: string): {
|
|
168
|
+
allowed: boolean;
|
|
169
|
+
source: string;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Build the adapter over a live catalog. */
|
|
173
|
+
export declare function createMagpieAdapter(catalog: ModelCatalog): MagpieAdapter;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness GenerateOptions -> pi-ai Context conversion, with image support.
|
|
3
|
+
*
|
|
4
|
+
* Text-only requests convert synchronously (`toPiContext`); requests carrying
|
|
5
|
+
* durable image blocks resolve bytes through the harness attachment service
|
|
6
|
+
* (`toPiContextWithImages`, async). pi-ai then encodes images as OpenAI
|
|
7
|
+
* `image_url` (completions) or `input_image` (responses) parts.
|
|
8
|
+
*/
|
|
9
|
+
export interface ImageAttachmentRef {
|
|
10
|
+
attachmentId: string;
|
|
11
|
+
mediaType: string;
|
|
12
|
+
bytes: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
name?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface FileAttachmentRef {
|
|
18
|
+
attachmentId: string;
|
|
19
|
+
name: string;
|
|
20
|
+
bytes: number;
|
|
21
|
+
}
|
|
22
|
+
export interface HarnessTool {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
parameters: unknown;
|
|
26
|
+
}
|
|
27
|
+
export type HarnessBlock = {
|
|
28
|
+
type: 'text';
|
|
29
|
+
text: string;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'reasoning';
|
|
32
|
+
text: string;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'tool-call';
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
arguments: string;
|
|
38
|
+
} | {
|
|
39
|
+
type: 'image';
|
|
40
|
+
attachment: ImageAttachmentRef;
|
|
41
|
+
offloaded?: true;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'file';
|
|
45
|
+
attachment: FileAttachmentRef;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'tool-result';
|
|
49
|
+
toolCallId: string;
|
|
50
|
+
content: HarnessBlock[];
|
|
51
|
+
isError?: boolean;
|
|
52
|
+
[key: string]: unknown;
|
|
53
|
+
} | {
|
|
54
|
+
type: string;
|
|
55
|
+
[key: string]: unknown;
|
|
56
|
+
};
|
|
57
|
+
export interface HarnessMessage {
|
|
58
|
+
role: 'system' | 'user' | 'assistant' | 'tool' | 'developer';
|
|
59
|
+
content: HarnessBlock[];
|
|
60
|
+
source?: {
|
|
61
|
+
kind: string;
|
|
62
|
+
provider?: string;
|
|
63
|
+
model?: string;
|
|
64
|
+
callId?: string;
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
};
|
|
67
|
+
toolCallId?: string;
|
|
68
|
+
isError?: boolean;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
export interface HarnessGenerateOptions {
|
|
72
|
+
provider: string;
|
|
73
|
+
model: string;
|
|
74
|
+
messages: HarnessMessage[];
|
|
75
|
+
system?: string;
|
|
76
|
+
tools?: HarnessTool[];
|
|
77
|
+
maxTokens?: number;
|
|
78
|
+
temperature?: number;
|
|
79
|
+
reasoningEffort?: string;
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
/** pi-ai message vocabulary (subset we emit). */
|
|
84
|
+
export type PiMessage = {
|
|
85
|
+
role: 'user';
|
|
86
|
+
content: string | PiContentBlock[];
|
|
87
|
+
timestamp: number;
|
|
88
|
+
} | {
|
|
89
|
+
role: 'assistant';
|
|
90
|
+
content: PiAssistantBlock[];
|
|
91
|
+
api: 'openai-completions' | 'openai-responses';
|
|
92
|
+
provider: string;
|
|
93
|
+
model: string;
|
|
94
|
+
usage: PiUsage;
|
|
95
|
+
stopReason: 'stop' | 'toolUse';
|
|
96
|
+
timestamp: number;
|
|
97
|
+
} | {
|
|
98
|
+
role: 'toolResult';
|
|
99
|
+
toolCallId: string;
|
|
100
|
+
toolName: string;
|
|
101
|
+
content: PiContentBlock[];
|
|
102
|
+
isError: boolean;
|
|
103
|
+
timestamp: number;
|
|
104
|
+
};
|
|
105
|
+
export type PiAssistantBlock = {
|
|
106
|
+
type: 'text';
|
|
107
|
+
text: string;
|
|
108
|
+
} | {
|
|
109
|
+
type: 'thinking';
|
|
110
|
+
thinking: string;
|
|
111
|
+
} | {
|
|
112
|
+
type: 'toolCall';
|
|
113
|
+
id: string;
|
|
114
|
+
name: string;
|
|
115
|
+
arguments: Record<string, unknown>;
|
|
116
|
+
};
|
|
117
|
+
export type PiContentBlock = {
|
|
118
|
+
type: 'text';
|
|
119
|
+
text: string;
|
|
120
|
+
} | {
|
|
121
|
+
type: 'image';
|
|
122
|
+
data: string;
|
|
123
|
+
mimeType: string;
|
|
124
|
+
};
|
|
125
|
+
export interface PiUsage {
|
|
126
|
+
input: number;
|
|
127
|
+
output: number;
|
|
128
|
+
cacheRead: number;
|
|
129
|
+
cacheWrite: number;
|
|
130
|
+
totalTokens: number;
|
|
131
|
+
cost: {
|
|
132
|
+
input: number;
|
|
133
|
+
output: number;
|
|
134
|
+
cacheRead: number;
|
|
135
|
+
cacheWrite: number;
|
|
136
|
+
total: number;
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export interface PiTool {
|
|
140
|
+
name: string;
|
|
141
|
+
description: string;
|
|
142
|
+
parameters: unknown;
|
|
143
|
+
}
|
|
144
|
+
export interface PiContext {
|
|
145
|
+
systemPrompt?: string;
|
|
146
|
+
messages: PiMessage[];
|
|
147
|
+
tools?: PiTool[];
|
|
148
|
+
}
|
|
149
|
+
/** Attachment service surface we consume (structural subset of dsh-attachment). */
|
|
150
|
+
export interface AttachmentStore {
|
|
151
|
+
readImageRequest(ref: ImageAttachmentRef, target: {
|
|
152
|
+
width: number;
|
|
153
|
+
height: number;
|
|
154
|
+
maxBytes: number;
|
|
155
|
+
}, signal?: AbortSignal): Promise<{
|
|
156
|
+
data: Uint8Array;
|
|
157
|
+
mediaType: string;
|
|
158
|
+
bytes: number;
|
|
159
|
+
width: number;
|
|
160
|
+
height: number;
|
|
161
|
+
}>;
|
|
162
|
+
}
|
|
163
|
+
export interface RequestImageVersion {
|
|
164
|
+
data: Uint8Array;
|
|
165
|
+
mediaType: string;
|
|
166
|
+
bytes: number;
|
|
167
|
+
width: number;
|
|
168
|
+
height: number;
|
|
169
|
+
}
|
|
170
|
+
export declare function zeroUsage(): PiUsage;
|
|
171
|
+
/** True when any message carries a retained (non-offloaded) image block. */
|
|
172
|
+
export declare function contentHasImage(messages: readonly HarnessMessage[]): boolean;
|
|
173
|
+
/** Request target for one attachment: fit within the pixel budget, cap bytes. */
|
|
174
|
+
export declare function requestImageTarget(ref: ImageAttachmentRef): {
|
|
175
|
+
width: number;
|
|
176
|
+
height: number;
|
|
177
|
+
maxBytes: number;
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Resolve every retained image to request bytes. Offloaded occurrences need
|
|
181
|
+
* no bytes (placeholder text only) and are skipped here.
|
|
182
|
+
*/
|
|
183
|
+
export declare function prepareRequestImages(messages: readonly HarnessMessage[], attachments: AttachmentStore, signal?: AbortSignal): Promise<Map<string, RequestImageVersion>>;
|
|
184
|
+
/** User/tool-result content with images resolved to handle text + bytes. */
|
|
185
|
+
export declare function userContentWithImages(blocks: HarnessBlock[], requestImages: Map<string, RequestImageVersion>): string | PiContentBlock[];
|
|
186
|
+
/**
|
|
187
|
+
* Text-only conversion (no image bytes needed). Images without resolved
|
|
188
|
+
* bytes cannot be sent — callers must route image-bearing requests through
|
|
189
|
+
* `toPiContextWithImages` instead.
|
|
190
|
+
*/
|
|
191
|
+
export declare function toPiContext(options: HarnessGenerateOptions, entryEndpoints?: readonly string[]): PiContext;
|
|
192
|
+
/**
|
|
193
|
+
* Image-aware conversion: retained images resolve to request bytes via the
|
|
194
|
+
* attachment service; offloaded images become placeholder text.
|
|
195
|
+
*/
|
|
196
|
+
export declare function toPiContextWithImages(options: HarnessGenerateOptions, attachments: AttachmentStore, entryEndpoints?: readonly string[]): Promise<PiContext>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stall watchdog for upstream event streams.
|
|
3
|
+
*
|
|
4
|
+
* pi-ai surfaces transport failures as `error` events, but a silently
|
|
5
|
+
* stalled SSE connection (headers sent, then no bytes ever again) produces
|
|
6
|
+
* *no* event at all: the consumer's `for await` blocks until the SDK-level
|
|
7
|
+
* overall timeout or the harness timeout. This wrapper races every `next()`
|
|
8
|
+
* against a timer and, on expiry, aborts the upstream (best effort, via
|
|
9
|
+
* `onTimeout`) and yields a synthetic `error` event so the normal chunk
|
|
10
|
+
* translation turns the stall into a fast `TIMEOUT` finish instead of a
|
|
11
|
+
* minutes-long hang.
|
|
12
|
+
*
|
|
13
|
+
* Two phases: time-to-first-event (queueing happens here) and idle-between-
|
|
14
|
+
* events (a healthy stream emits frequently). Either phase accepts a
|
|
15
|
+
* non-positive value to disable its timer (wait forever, the old behavior).
|
|
16
|
+
*/
|
|
17
|
+
import type { PiEvent } from './events.ts';
|
|
18
|
+
export interface StallTimeouts {
|
|
19
|
+
/** Max wait for the first upstream event (connection + queueing). */
|
|
20
|
+
firstEventTimeoutMs: number;
|
|
21
|
+
/** Max silence between two upstream events once streaming. */
|
|
22
|
+
idleTimeoutMs: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function withStallTimeout(events: AsyncIterable<PiEvent>, timeouts: StallTimeouts, options?: {
|
|
25
|
+
model?: string;
|
|
26
|
+
onTimeout?: () => void;
|
|
27
|
+
}): AsyncGenerator<PiEvent>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const NS = "magpie-connect";
|
|
2
|
+
type Dict = Record<string, string>;
|
|
3
|
+
export declare const EN: Dict;
|
|
4
|
+
export declare const ZH: Dict;
|
|
5
|
+
export interface LocaleRuntime {
|
|
6
|
+
subscribe(listener: () => void): () => void;
|
|
7
|
+
getSnapshot(): {
|
|
8
|
+
active?: string;
|
|
9
|
+
revision?: number;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function setLocaleRuntime(runtime: LocaleRuntime | undefined): void;
|
|
13
|
+
export declare function text(key: string, params?: Record<string, unknown>): string;
|
|
14
|
+
export declare function useLocaleRevision(): void;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NS } from './i18n.ts';
|
|
2
|
+
interface Slots {
|
|
3
|
+
inject(name: string, fn: () => unknown): unknown;
|
|
4
|
+
register(entry: {
|
|
5
|
+
name: string;
|
|
6
|
+
id: string;
|
|
7
|
+
order: number;
|
|
8
|
+
locale?: string;
|
|
9
|
+
label?: () => string;
|
|
10
|
+
}, component: unknown): unknown;
|
|
11
|
+
}
|
|
12
|
+
interface ClientLocale {
|
|
13
|
+
register(ns: string, dicts: {
|
|
14
|
+
zh: Record<string, string>;
|
|
15
|
+
en: Record<string, string>;
|
|
16
|
+
}): unknown;
|
|
17
|
+
register(ns: string, lang: string, dict: Record<string, string>): () => void;
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
getSnapshot(): {
|
|
20
|
+
active?: string;
|
|
21
|
+
revision?: number;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface ClientContext {
|
|
25
|
+
locale: ClientLocale;
|
|
26
|
+
slots: Slots;
|
|
27
|
+
effect(fn: () => () => void, label?: string): unknown;
|
|
28
|
+
}
|
|
29
|
+
export declare function apply(ctx: ClientContext): void;
|
|
30
|
+
export declare const inject: string[];
|
|
31
|
+
export { NS };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CandidateRow, type ModelRow } from './model-visibility.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The model list of the Magpie gateway, plus the action that asks the gateway
|
|
4
|
+
* what it serves.
|
|
5
|
+
*
|
|
6
|
+
* The list shows only *enabled* models: this plugin's picker is the gateway's
|
|
7
|
+
* whole catalog minus a hidden set, so a hidden row would be a row that is
|
|
8
|
+
* invisible everywhere it matters. Hidden models live in the fetch dialog
|
|
9
|
+
* instead, which is the one surface that can both show and re-enable them.
|
|
10
|
+
* That is also why a row's control is a delete button rather than a
|
|
11
|
+
* checkbox — with nothing hidden on screen, unticking and deleting are the
|
|
12
|
+
* same act, and only deleting reads correctly.
|
|
13
|
+
*
|
|
14
|
+
* Fetching asks the endpoint **the form currently shows** — including a key
|
|
15
|
+
* typed but not yet saved — so filling in a gateway is one pass instead of
|
|
16
|
+
* save-then-return. The reply is candidates the user picks from, never
|
|
17
|
+
* configuration written behind them.
|
|
18
|
+
*/
|
|
19
|
+
export type { CandidateRow, ModelRow };
|
|
20
|
+
/** Props of {@link ModelListEditor}. */
|
|
21
|
+
export interface ModelListEditorProps {
|
|
22
|
+
/** The rows as currently drafted (the gateway's directory). */
|
|
23
|
+
models: readonly ModelRow[];
|
|
24
|
+
/** The ids the picker currently offers (the drafted rows minus the hidden set). */
|
|
25
|
+
visibleIds: ReadonlySet<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Ask the gateway for its current catalog. Rejects with a display-ready
|
|
28
|
+
* message; the editor owns showing it.
|
|
29
|
+
*/
|
|
30
|
+
onFetch: () => Promise<readonly CandidateRow[]>;
|
|
31
|
+
/** Hide one model (the list's delete action). */
|
|
32
|
+
onRemove: (id: string) => void;
|
|
33
|
+
/** Replace the whole hidden set (the dialog's adopt action). */
|
|
34
|
+
onHiddenChange: (hidden: string[]) => void;
|
|
35
|
+
/** Disable every control (a pending save/probe). */
|
|
36
|
+
disabled: boolean;
|
|
37
|
+
/** Whether the connection fields are filled in enough to ask the gateway. */
|
|
38
|
+
fetchable: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Autosave state for the list itself. Shown inline on this card, not on the
|
|
41
|
+
* connection card above: the user's eye is here when they remove a row, and
|
|
42
|
+
* a write failure must land where the change was made.
|
|
43
|
+
*/
|
|
44
|
+
status: 'idle' | 'saving' | 'saved' | 'error';
|
|
45
|
+
/** Autosave failure message, when {@link status} is `error`. */
|
|
46
|
+
failure?: string | undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Render the model list with its fetch action.
|
|
50
|
+
* @param props - the drafted rows, the current visibility set, and the fetch/toggle callbacks.
|
|
51
|
+
* @returns the model-list editor.
|
|
52
|
+
*/
|
|
53
|
+
export declare function ModelListEditor(props: ModelListEditorProps): unknown;
|