@ryanfw/prompt-orchestration-pipeline 1.3.4 → 1.3.5
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/docs/http-api.md +3 -0
- package/docs/pop-task-guide.md +8 -0
- package/package.json +5 -2
- package/src/cli/__tests__/analyze-task.test.ts +1 -2
- package/src/config/__tests__/config-schema.test.ts +42 -0
- package/src/config/__tests__/legacy-literal-guard.test.ts +156 -0
- package/src/config/__tests__/models.test.ts +139 -1
- package/src/config/models.ts +121 -19
- package/src/core/__tests__/agent-step.test.ts +52 -0
- package/src/core/__tests__/job-view.test.ts +98 -0
- package/src/core/agent-step.ts +5 -1
- package/src/core/agent-types.ts +6 -1
- package/src/core/job-view.ts +21 -1
- package/src/llm/__tests__/dispatch.test.ts +130 -0
- package/src/llm/__tests__/index.test.ts +17 -18
- package/src/llm/__tests__/legacy-normalization.test.ts +109 -0
- package/src/llm/index.ts +109 -112
- package/src/providers/__tests__/claude-code.test.ts +9 -3
- package/src/providers/__tests__/moonshot.test.ts +33 -3
- package/src/providers/__tests__/types.test.ts +43 -0
- package/src/providers/__tests__/zhipu.test.ts +24 -28
- package/src/providers/claude-code.ts +2 -2
- package/src/providers/moonshot.ts +7 -0
- package/src/providers/types.ts +7 -5
- package/src/providers/zhipu.ts +0 -2
- package/src/task-analysis/__tests__/analyzer.test.ts +0 -0
- package/src/task-analysis/__tests__/enrichers-schema-deducer.test.ts +36 -1
- package/src/task-analysis/analyzer.ts +4 -10
- package/src/task-analysis/enrichers/schema-deducer.ts +8 -9
- package/src/ui/dist/assets/{index-DN3-zvtP.js → index-lBbVeNZC.js} +5 -2
- package/src/ui/dist/assets/{index-DN3-zvtP.js.map → index-lBbVeNZC.js.map} +1 -1
- package/src/ui/dist/index.html +1 -1
- package/src/ui/pages/Code.tsx +5 -2
- package/src/ui/server/__tests__/http-api-contract.test.ts +838 -0
- package/src/ui/server/__tests__/job-control-endpoints.test.ts +134 -27
- package/src/ui/server/__tests__/job-control-service.test.ts +282 -0
- package/src/ui/server/__tests__/job-process-registry.test.ts +151 -0
- package/src/ui/server/__tests__/job-repository.test.ts +142 -0
- package/src/ui/server/config-bridge.ts +9 -6
- package/src/ui/server/embedded-assets.ts +4 -4
- package/src/ui/server/endpoints/gate-endpoints.ts +24 -12
- package/src/ui/server/endpoints/job-control-endpoints.ts +19 -763
- package/src/ui/server/job-control-service.ts +520 -0
- package/src/ui/server/job-process-registry.ts +250 -0
- package/src/ui/server/job-repository.ts +316 -0
package/src/llm/index.ts
CHANGED
|
@@ -20,9 +20,14 @@ import { ensureMessagesPresent, isJsonResponseFormat } from "../providers/base.t
|
|
|
20
20
|
import {
|
|
21
21
|
MODEL_CONFIG,
|
|
22
22
|
PROVIDER_FUNCTIONS,
|
|
23
|
+
PROVIDERS,
|
|
23
24
|
getModelConfig,
|
|
25
|
+
normalizeLegacyProvider,
|
|
26
|
+
} from "../config/models.ts";
|
|
27
|
+
import type {
|
|
28
|
+
ProviderFunctionEntry,
|
|
29
|
+
ModelCatalogProviderName,
|
|
24
30
|
} from "../config/models.ts";
|
|
25
|
-
import type { ProviderFunctionsIndex } from "../config/models.ts";
|
|
26
31
|
import type {
|
|
27
32
|
ChatOptions,
|
|
28
33
|
ChatResponse,
|
|
@@ -44,22 +49,6 @@ import type {
|
|
|
44
49
|
} from "../providers/types.ts";
|
|
45
50
|
import { ProviderJsonParseError } from "../providers/types.ts";
|
|
46
51
|
|
|
47
|
-
// ─── Provider Name Mapping ───────────────────────────────────────────────────
|
|
48
|
-
// types.ts uses "claudecode" (one word), config/models.ts uses "claude-code" (hyphenated).
|
|
49
|
-
|
|
50
|
-
/** Maps gateway ProviderName to config provider key. */
|
|
51
|
-
function toConfigProvider(name: ProviderName): string {
|
|
52
|
-
if (name === "claudecode") return "claude-code";
|
|
53
|
-
if (name === "zhipu") return "zai";
|
|
54
|
-
return name;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Maps config provider key to gateway ProviderName. */
|
|
58
|
-
function fromConfigProvider(configName: string): ProviderName {
|
|
59
|
-
if (configName === "claude-code") return "claudecode";
|
|
60
|
-
return configName as ProviderName;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
52
|
// ─── Module State ────────────────────────────────────────────────────────────
|
|
64
53
|
|
|
65
54
|
const llmEvents = new EventEmitter();
|
|
@@ -73,68 +62,82 @@ function warnUnknownAliasOnce(alias: string): void {
|
|
|
73
62
|
console.warn(`[llm] unknown model alias "${alias}"; reporting cost as estimated`);
|
|
74
63
|
}
|
|
75
64
|
|
|
65
|
+
function normalizeProviderOrThrow(name: string): ProviderName {
|
|
66
|
+
const canonical = normalizeLegacyProvider(name);
|
|
67
|
+
if (!canonical) {
|
|
68
|
+
throw new Error(`Unknown provider: ${name}`);
|
|
69
|
+
}
|
|
70
|
+
return canonical;
|
|
71
|
+
}
|
|
72
|
+
|
|
76
73
|
// ─── Adapter Dispatch ────────────────────────────────────────────────────────
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return openaiChat({
|
|
94
|
-
messages, model, temperature, maxTokens, responseFormat, topP, stop, maxRetries, requestTimeoutMs,
|
|
95
|
-
seed: undefined,
|
|
96
|
-
frequencyPenalty: options.frequencyPenalty,
|
|
97
|
-
presencePenalty: options.presencePenalty,
|
|
98
|
-
});
|
|
99
|
-
case "gemini":
|
|
100
|
-
return geminiChat({
|
|
101
|
-
messages, model, temperature, maxTokens, responseFormat, topP, stop, maxRetries, requestTimeoutMs,
|
|
102
|
-
frequencyPenalty: options.frequencyPenalty,
|
|
103
|
-
presencePenalty: options.presencePenalty,
|
|
104
|
-
});
|
|
105
|
-
case "deepseek":
|
|
106
|
-
return deepseekChat({
|
|
107
|
-
messages, model, temperature, maxTokens, responseFormat, topP, stop, maxRetries, requestTimeoutMs,
|
|
108
|
-
frequencyPenalty: options.frequencyPenalty,
|
|
109
|
-
presencePenalty: options.presencePenalty,
|
|
110
|
-
});
|
|
111
|
-
case "moonshot":
|
|
112
|
-
return moonshotChat({ messages, model, maxTokens, responseFormat, maxRetries, requestTimeoutMs });
|
|
113
|
-
case "zai":
|
|
114
|
-
case "zhipu":
|
|
115
|
-
return zaiChat({ messages, model, temperature, maxTokens, responseFormat, topP, stop, maxRetries, requestTimeoutMs });
|
|
116
|
-
case "claudecode":
|
|
117
|
-
return claudeCodeChat({ messages, model, maxTokens, responseFormat, maxRetries, requestTimeoutMs });
|
|
118
|
-
case "opencode":
|
|
119
|
-
return opencodeChat({
|
|
120
|
-
messages, model, responseFormat, maxRetries, requestTimeoutMs,
|
|
121
|
-
opencode: options.opencode,
|
|
122
|
-
});
|
|
123
|
-
case "mock": {
|
|
124
|
-
if (!mockProvider) {
|
|
125
|
-
throw new Error("No mock provider registered. Call registerMockProvider() first.");
|
|
126
|
-
}
|
|
127
|
-
return mockProvider.chat(options);
|
|
75
|
+
type AdapterFn = (opts: ChatOptions) => Promise<AdapterResponse>;
|
|
76
|
+
|
|
77
|
+
const ADAPTERS: Record<ProviderName, AdapterFn> = {
|
|
78
|
+
openai: (opts) => openaiChat(opts),
|
|
79
|
+
anthropic: (opts) => anthropicChat(opts),
|
|
80
|
+
gemini: (opts) => geminiChat(opts),
|
|
81
|
+
deepseek: (opts) => deepseekChat(opts),
|
|
82
|
+
moonshot: (opts) => moonshotChat(opts),
|
|
83
|
+
zai: (opts) => zaiChat(opts),
|
|
84
|
+
alibaba: (opts) => alibabaChat(opts),
|
|
85
|
+
"claude-code": (opts) => claudeCodeChat(opts),
|
|
86
|
+
opencode: (opts) => opencodeChat(opts),
|
|
87
|
+
mock: (opts) => {
|
|
88
|
+
if (!mockProvider) {
|
|
89
|
+
throw new Error("No mock provider registered. Call registerMockProvider() first.");
|
|
128
90
|
}
|
|
129
|
-
|
|
130
|
-
|
|
91
|
+
return mockProvider.chat(opts);
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const CLI_AVAILABILITY: Partial<Record<ProviderName, () => boolean>> = {
|
|
96
|
+
"claude-code": isClaudeCodeAvailable,
|
|
97
|
+
opencode: isOpenCodeAvailable,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
function buildAdapterOptions(options: ChatOptions): ChatOptions {
|
|
101
|
+
const declared = PROVIDERS[options.provider].sampling;
|
|
102
|
+
const opts: ChatOptions = {
|
|
103
|
+
provider: options.provider,
|
|
104
|
+
messages: options.messages,
|
|
105
|
+
model: options.model,
|
|
106
|
+
maxTokens: options.maxTokens,
|
|
107
|
+
responseFormat: options.responseFormat,
|
|
108
|
+
maxRetries: options.maxRetries,
|
|
109
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
110
|
+
};
|
|
111
|
+
if (options.opencode !== undefined) {
|
|
112
|
+
opts.opencode = options.opencode;
|
|
113
|
+
}
|
|
114
|
+
if (declared.includes("temperature") && options.temperature !== undefined) {
|
|
115
|
+
opts.temperature = options.temperature;
|
|
116
|
+
}
|
|
117
|
+
if (declared.includes("topP") && options.topP !== undefined) {
|
|
118
|
+
opts.topP = options.topP;
|
|
119
|
+
}
|
|
120
|
+
if (declared.includes("stop") && options.stop !== undefined) {
|
|
121
|
+
opts.stop = options.stop;
|
|
131
122
|
}
|
|
123
|
+
if (declared.includes("frequencyPenalty") && options.frequencyPenalty !== undefined) {
|
|
124
|
+
opts.frequencyPenalty = options.frequencyPenalty;
|
|
125
|
+
}
|
|
126
|
+
if (declared.includes("presencePenalty") && options.presencePenalty !== undefined) {
|
|
127
|
+
opts.presencePenalty = options.presencePenalty;
|
|
128
|
+
}
|
|
129
|
+
return opts;
|
|
132
130
|
}
|
|
133
131
|
|
|
134
132
|
async function callAdapter(
|
|
135
133
|
options: ChatOptions,
|
|
136
134
|
): Promise<AdapterResponse> {
|
|
137
|
-
const
|
|
135
|
+
const adapter = ADAPTERS[options.provider] as AdapterFn | undefined;
|
|
136
|
+
if (!adapter) {
|
|
137
|
+
throw new Error(`Unknown provider: ${options.provider as string}`);
|
|
138
|
+
}
|
|
139
|
+
const adapterOptions = buildAdapterOptions(options);
|
|
140
|
+
const response = await adapter(adapterOptions);
|
|
138
141
|
response.usageAvailable = resolveUsageAvailable(options.provider, options);
|
|
139
142
|
return response;
|
|
140
143
|
}
|
|
@@ -173,14 +176,14 @@ async function performJsonRepair(
|
|
|
173
176
|
/**
|
|
174
177
|
* Returns whether a provider can structurally report token usage for the given
|
|
175
178
|
* options. This is the single source of truth for `usageAvailable`; adapters
|
|
176
|
-
* never set it themselves.
|
|
177
|
-
*
|
|
179
|
+
* never set it themselves. The base capability comes from
|
|
180
|
+
* `PROVIDERS[provider].usageAvailable`; OpenCode keeps its runtime mode/baseUrl
|
|
181
|
+
* exception because SDK mode reports usage while CLI mode does not.
|
|
178
182
|
*/
|
|
179
183
|
export function resolveUsageAvailable(
|
|
180
184
|
provider: ProviderName,
|
|
181
185
|
options: ChatOptions,
|
|
182
186
|
): boolean {
|
|
183
|
-
if (provider === "claudecode") return false;
|
|
184
187
|
if (provider === "opencode") {
|
|
185
188
|
if (options.opencode?.mode === "cli") return false;
|
|
186
189
|
if (options.opencode?.mode === "sdk") return true;
|
|
@@ -190,7 +193,7 @@ export function resolveUsageAvailable(
|
|
|
190
193
|
process.env.OPENCODE_BASE_URL;
|
|
191
194
|
return baseUrl != null;
|
|
192
195
|
}
|
|
193
|
-
return
|
|
196
|
+
return PROVIDERS[provider].usageAvailable;
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
// ─── Usage Normalization ─────────────────────────────────────────────────────
|
|
@@ -352,6 +355,8 @@ async function writeDebugLog(options: ChatOptions, response: ChatResponse): Prom
|
|
|
352
355
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
353
356
|
|
|
354
357
|
export async function chat(options: ChatOptions): Promise<ChatResponse> {
|
|
358
|
+
const provider = normalizeProviderOrThrow(options.provider);
|
|
359
|
+
options = { ...options, provider };
|
|
355
360
|
ensureMessagesPresent(options.messages, options.provider);
|
|
356
361
|
const configTimeout = getConfig().taskRunner.llmRequestTimeout;
|
|
357
362
|
const opts = {
|
|
@@ -415,7 +420,7 @@ export async function chat(options: ChatOptions): Promise<ChatResponse> {
|
|
|
415
420
|
: 0;
|
|
416
421
|
const costEstimated = price === null || usage.source !== "reported";
|
|
417
422
|
if (price === null) {
|
|
418
|
-
warnUnknownAliasOnce(`${
|
|
423
|
+
warnUnknownAliasOnce(`${opts.provider}:${model}`);
|
|
419
424
|
}
|
|
420
425
|
const duration = Date.now() - startTime;
|
|
421
426
|
|
|
@@ -474,7 +479,7 @@ export async function complete(
|
|
|
474
479
|
prompt: string,
|
|
475
480
|
options?: Partial<ChatOptions>,
|
|
476
481
|
): Promise<ChatResponse> {
|
|
477
|
-
const defaultProvider = getConfig().llm.defaultProvider
|
|
482
|
+
const defaultProvider = normalizeProviderOrThrow(getConfig().llm.defaultProvider);
|
|
478
483
|
return chat({
|
|
479
484
|
provider: defaultProvider,
|
|
480
485
|
messages: [{ role: "user", content: prompt }],
|
|
@@ -512,6 +517,7 @@ export function createHighLevelLLM(options?: Partial<ChatOptions>): HighLevelLLM
|
|
|
512
517
|
export function createLLMWithOverride(
|
|
513
518
|
override: { provider: ProviderName; model: string },
|
|
514
519
|
): HighLevelLLM {
|
|
520
|
+
const canonicalProvider = normalizeProviderOrThrow(override.provider);
|
|
515
521
|
const base = createHighLevelLLM();
|
|
516
522
|
|
|
517
523
|
const GUARDED_METHODS = new Set([
|
|
@@ -538,11 +544,11 @@ export function createLLMWithOverride(
|
|
|
538
544
|
// If accessing known top-level methods, return them bound with override
|
|
539
545
|
if (prop === "chat") {
|
|
540
546
|
return (opts: ChatOptions) =>
|
|
541
|
-
chat({ ...opts, provider:
|
|
547
|
+
chat({ ...opts, provider: canonicalProvider, model: override.model });
|
|
542
548
|
}
|
|
543
549
|
if (prop === "complete") {
|
|
544
550
|
return (prompt: string, opts?: Partial<ChatOptions>) =>
|
|
545
|
-
complete(prompt, { ...opts, provider:
|
|
551
|
+
complete(prompt, { ...opts, provider: canonicalProvider, model: override.model });
|
|
546
552
|
}
|
|
547
553
|
if (prop === "createChain" || prop === "withRetry" || prop === "parallel" || prop === "getAvailableProviders") {
|
|
548
554
|
return Reflect.get(target, prop, receiver);
|
|
@@ -560,7 +566,7 @@ export function createLLMWithOverride(
|
|
|
560
566
|
return (opts?: Partial<ChatOptions>) =>
|
|
561
567
|
chat({
|
|
562
568
|
...opts,
|
|
563
|
-
provider:
|
|
569
|
+
provider: canonicalProvider,
|
|
564
570
|
model: override.model,
|
|
565
571
|
} as ChatOptions);
|
|
566
572
|
},
|
|
@@ -571,7 +577,7 @@ export function createLLMWithOverride(
|
|
|
571
577
|
return (opts?: Partial<ChatOptions>) =>
|
|
572
578
|
chat({
|
|
573
579
|
...opts,
|
|
574
|
-
provider:
|
|
580
|
+
provider: canonicalProvider,
|
|
575
581
|
model: override.model,
|
|
576
582
|
} as ChatOptions);
|
|
577
583
|
},
|
|
@@ -679,19 +685,18 @@ export function registerMockProvider(provider: MockProvider): void {
|
|
|
679
685
|
}
|
|
680
686
|
|
|
681
687
|
export function getAvailableProviders(): ProviderAvailability {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
};
|
|
688
|
+
const availability = {} as ProviderAvailability;
|
|
689
|
+
for (const name of Object.keys(PROVIDERS) as ProviderName[]) {
|
|
690
|
+
if (name === "mock") {
|
|
691
|
+
availability[name] = mockProvider !== null;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
const meta = PROVIDERS[name];
|
|
695
|
+
const envAvailable = meta.envKeys.some((key) => !!process.env[key]);
|
|
696
|
+
const cliAvailable = CLI_AVAILABILITY[name]?.() ?? false;
|
|
697
|
+
availability[name] = envAvailable || cliAvailable;
|
|
698
|
+
}
|
|
699
|
+
return availability;
|
|
695
700
|
}
|
|
696
701
|
|
|
697
702
|
export function estimateTokens(text: string): number {
|
|
@@ -704,7 +709,7 @@ export interface ModelPrice {
|
|
|
704
709
|
}
|
|
705
710
|
|
|
706
711
|
/**
|
|
707
|
-
* Looks up the per-million-token price for a
|
|
712
|
+
* Looks up the per-million-token price for a `(provider, model)` pair.
|
|
708
713
|
* Returns `null` when no price is known. This is the single cost-lookup surface
|
|
709
714
|
* used by both `calculateCost` and the `costEstimated` discriminator in `chat()`.
|
|
710
715
|
*/
|
|
@@ -712,8 +717,9 @@ export function findModelPrice(
|
|
|
712
717
|
provider: string,
|
|
713
718
|
model: string,
|
|
714
719
|
): ModelPrice | null {
|
|
715
|
-
const
|
|
716
|
-
|
|
720
|
+
const canonical = normalizeLegacyProvider(provider);
|
|
721
|
+
if (!canonical) return null;
|
|
722
|
+
const alias = `${canonical}:${model}`;
|
|
717
723
|
const direct = getModelConfig(alias);
|
|
718
724
|
if (direct) {
|
|
719
725
|
return {
|
|
@@ -722,7 +728,7 @@ export function findModelPrice(
|
|
|
722
728
|
};
|
|
723
729
|
}
|
|
724
730
|
for (const entry of Object.values(MODEL_CONFIG)) {
|
|
725
|
-
if (entry.provider ===
|
|
731
|
+
if (entry.provider === canonical && entry.model === model) {
|
|
726
732
|
return {
|
|
727
733
|
tokenCostInPerMillion: entry.tokenCostInPerMillion,
|
|
728
734
|
tokenCostOutPerMillion: entry.tokenCostOutPerMillion,
|
|
@@ -749,31 +755,22 @@ export function calculateCost(
|
|
|
749
755
|
function buildProviderModelMap(): ProviderModelMap {
|
|
750
756
|
const map: ProviderModelMap = {};
|
|
751
757
|
|
|
752
|
-
for (const [
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
758
|
+
for (const [providerName, entries] of Object.entries(PROVIDER_FUNCTIONS) as [
|
|
759
|
+
ModelCatalogProviderName,
|
|
760
|
+
ProviderFunctionEntry[],
|
|
761
|
+
][]) {
|
|
756
762
|
const group: ProviderGroup = {};
|
|
757
763
|
|
|
758
764
|
for (const entry of entries) {
|
|
759
765
|
group[entry.functionName] = (opts?: Partial<ChatOptions>) =>
|
|
760
766
|
chat({
|
|
761
|
-
provider:
|
|
767
|
+
provider: providerName,
|
|
762
768
|
model: entry.model,
|
|
763
769
|
...opts,
|
|
764
770
|
} as ChatOptions);
|
|
765
771
|
}
|
|
766
772
|
|
|
767
|
-
map[
|
|
768
|
-
|
|
769
|
-
// Preserve config-key access for callers still using hyphenated config names.
|
|
770
|
-
if (configProviderName !== gatewayProvider) {
|
|
771
|
-
map[configProviderName] = group;
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
if (gatewayProvider === "zai") {
|
|
775
|
-
map["zhipu"] = group;
|
|
776
|
-
}
|
|
773
|
+
map[providerName] = group;
|
|
777
774
|
}
|
|
778
775
|
|
|
779
776
|
return map;
|
|
@@ -125,9 +125,15 @@ describe("claudeCodeChat", () => {
|
|
|
125
125
|
const plainText = "This is not JSON";
|
|
126
126
|
spawnMock.mockReturnValue(createMockProcFromText(plainText));
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
try {
|
|
129
|
+
await claudeCodeChat(baseOptions);
|
|
130
|
+
expect.unreachable("should have thrown");
|
|
131
|
+
} catch (err) {
|
|
132
|
+
expect(err).toBeInstanceOf(ProviderJsonParseError);
|
|
133
|
+
const parseErr = err as ProviderJsonParseError;
|
|
134
|
+
expect(parseErr.provider).toBe("claude-code");
|
|
135
|
+
expect(parseErr.model).toBe("sonnet");
|
|
136
|
+
}
|
|
131
137
|
});
|
|
132
138
|
|
|
133
139
|
it("combines system and user messages into the prompt", async () => {
|
|
@@ -175,7 +175,7 @@ describe("moonshotChat", () => {
|
|
|
175
175
|
expect(headers["Content-Type"]).toBe("application/json");
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
-
it("
|
|
178
|
+
it("omits temperature, top_p, and stop from request body when not supplied", async () => {
|
|
179
179
|
const events = makeOpenAiSseEvents([JSON.stringify({ ok: true })]);
|
|
180
180
|
fetchMock.mockResolvedValue(mockStreamingResponse(events));
|
|
181
181
|
|
|
@@ -186,8 +186,38 @@ describe("moonshotChat", () => {
|
|
|
186
186
|
);
|
|
187
187
|
expect(body.temperature).toBeUndefined();
|
|
188
188
|
expect(body.top_p).toBeUndefined();
|
|
189
|
-
expect(body.
|
|
190
|
-
|
|
189
|
+
expect(body.stop).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("includes temperature, top_p, and stop in request body when supplied", async () => {
|
|
193
|
+
const events = makeOpenAiSseEvents([JSON.stringify({ ok: true })]);
|
|
194
|
+
fetchMock.mockResolvedValue(mockStreamingResponse(events));
|
|
195
|
+
|
|
196
|
+
await moonshotChat({
|
|
197
|
+
...baseOptions,
|
|
198
|
+
temperature: 0.4,
|
|
199
|
+
topP: 0.8,
|
|
200
|
+
stop: ["\n\nUser:"],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const body = JSON.parse(
|
|
204
|
+
(fetchMock.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
205
|
+
);
|
|
206
|
+
expect(body.temperature).toBe(0.4);
|
|
207
|
+
expect(body.top_p).toBe(0.8);
|
|
208
|
+
expect(body.stop).toEqual(["\n\nUser:"]);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("passes stop as a string when given a string", async () => {
|
|
212
|
+
const events = makeOpenAiSseEvents([JSON.stringify({ ok: true })]);
|
|
213
|
+
fetchMock.mockResolvedValue(mockStreamingResponse(events));
|
|
214
|
+
|
|
215
|
+
await moonshotChat({ ...baseOptions, stop: "END" });
|
|
216
|
+
|
|
217
|
+
const body = JSON.parse(
|
|
218
|
+
(fetchMock.mock.calls[0] as [string, RequestInit])[1].body as string,
|
|
219
|
+
);
|
|
220
|
+
expect(body.stop).toBe("END");
|
|
191
221
|
});
|
|
192
222
|
|
|
193
223
|
describe("content-filter fallback to DeepSeek", () => {
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type ChatOptions,
|
|
6
6
|
type LLMRequestCompleteEvent,
|
|
7
7
|
type LLMRequestErrorEvent,
|
|
8
|
+
type MoonshotOptions,
|
|
8
9
|
type NormalizedUsage,
|
|
9
10
|
type OpenCodeOptions,
|
|
10
11
|
type OpenCodePermissionAction,
|
|
@@ -13,6 +14,8 @@ import {
|
|
|
13
14
|
type OpenCodePermissionName,
|
|
14
15
|
type OpenCodePermissionRule,
|
|
15
16
|
type OpenCodeRequestConfig,
|
|
17
|
+
type ProviderAvailability,
|
|
18
|
+
type ProviderName,
|
|
16
19
|
type UsageSource,
|
|
17
20
|
} from "../types.ts";
|
|
18
21
|
|
|
@@ -192,6 +195,46 @@ describe("OpenCode types", () => {
|
|
|
192
195
|
});
|
|
193
196
|
});
|
|
194
197
|
|
|
198
|
+
describe("provider type contracts", () => {
|
|
199
|
+
it("ProviderName excludes the legacy zhipu and claudecode spellings", () => {
|
|
200
|
+
type HasLegacy = "zhipu" | "claudecode" extends ProviderName ? true : false;
|
|
201
|
+
const legacyExcluded: HasLegacy = false;
|
|
202
|
+
expect(legacyExcluded).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("ProviderName includes canonical claude-code and zai", () => {
|
|
206
|
+
type HasClaudeCode = "claude-code" extends ProviderName ? true : false;
|
|
207
|
+
type HasZai = "zai" extends ProviderName ? true : false;
|
|
208
|
+
const claudeCodePresent: HasClaudeCode = true;
|
|
209
|
+
const zaiPresent: HasZai = true;
|
|
210
|
+
expect(claudeCodePresent).toBe(true);
|
|
211
|
+
expect(zaiPresent).toBe(true);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("ProviderAvailability is keyed by canonical claude-code, not legacy spellings", () => {
|
|
215
|
+
type HasClaudeCode =
|
|
216
|
+
"claude-code" extends keyof ProviderAvailability ? true : false;
|
|
217
|
+
type HasLegacyKeys =
|
|
218
|
+
"zhipu" | "claudecode" extends keyof ProviderAvailability ? true : false;
|
|
219
|
+
const claudeCodePresent: HasClaudeCode = true;
|
|
220
|
+
const legacyAbsent: HasLegacyKeys = false;
|
|
221
|
+
expect(claudeCodePresent).toBe(true);
|
|
222
|
+
expect(legacyAbsent).toBe(false);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("MoonshotOptions accepts temperature, topP, and stop sampling params", () => {
|
|
226
|
+
const options: MoonshotOptions = {
|
|
227
|
+
messages: [{ role: "user", content: "hello" }],
|
|
228
|
+
temperature: 0.5,
|
|
229
|
+
topP: 0.9,
|
|
230
|
+
stop: ["\n\n"],
|
|
231
|
+
};
|
|
232
|
+
expect(options.temperature).toBe(0.5);
|
|
233
|
+
expect(options.topP).toBe(0.9);
|
|
234
|
+
expect(options.stop).toEqual(["\n\n"]);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
195
238
|
describe("usage telemetry contracts", () => {
|
|
196
239
|
it("NormalizedUsage requires a source field", () => {
|
|
197
240
|
const usage: NormalizedUsage = {
|