melchizedek-agents 1.1.0 → 1.3.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/.env.example +19 -0
- package/AGENT_SETUP.md +3 -1
- package/CHANGELOG.md +373 -0
- package/DOCUMENTATION.md +193 -21
- package/ONBOARDING.md +378 -0
- package/QUICKSTART.md +15 -2
- package/README.md +26 -3
- package/config/agents/examples/connectors.yaml +82 -0
- package/config/agents/examples/council.yaml +1 -1
- package/config/agents/examples/critic.yaml +8 -8
- package/config/agents/examples/image_production.yaml +4 -3
- package/config/agents/syndicate.schema.json +389 -33
- package/config/agents/syndicateSchema.yaml +18 -6
- package/config/agents/templates/intake_extractor.yaml +3 -2
- package/config/agents/templates/systems_operator.yaml +5 -2
- package/dist/lib/a2a/app.d.ts +12 -0
- package/dist/lib/a2a/app.js +25 -11
- package/dist/lib/a2a/executor.js +14 -6
- package/dist/lib/a2a/oauthSetup.d.ts +16 -0
- package/dist/lib/a2a/oauthSetup.js +77 -8
- package/dist/lib/agentDialect.d.ts +18 -8
- package/dist/lib/agentDialect.js +36 -19
- package/dist/lib/chatgpt/adapter.d.ts +50 -0
- package/dist/lib/chatgpt/adapter.js +107 -0
- package/dist/lib/chatgpt/cli.d.ts +23 -0
- package/dist/lib/chatgpt/cli.js +77 -0
- package/dist/lib/chatgpt/oauth.d.ts +103 -0
- package/dist/lib/chatgpt/oauth.js +448 -0
- package/dist/lib/chatgpt/state.d.ts +98 -0
- package/dist/lib/chatgpt/state.js +145 -0
- package/dist/lib/chatgpt/store.d.ts +24 -0
- package/dist/lib/chatgpt/store.js +94 -0
- package/dist/lib/compile.d.ts +38 -7
- package/dist/lib/compile.js +97 -62
- package/dist/lib/compileNative.d.ts +11 -3
- package/dist/lib/compileNative.js +18 -1
- package/dist/lib/dispatch.js +6 -2
- package/dist/lib/doctor.d.ts +107 -0
- package/dist/lib/doctor.js +217 -7
- package/dist/lib/loadSyndicate.d.ts +15 -0
- package/dist/lib/mcp/server.d.ts +195 -0
- package/dist/lib/mcp/server.js +564 -0
- package/dist/lib/memory/supabaseMemoryService.js +2 -2
- package/dist/lib/models/adapterResolver.js +6 -1
- package/dist/lib/models/capabilities.d.ts +9 -0
- package/dist/lib/models/capabilities.js +24 -0
- package/dist/lib/models/chatCompletionsAdapter.d.ts +11 -3
- package/dist/lib/models/chatCompletionsAdapter.js +19 -5
- package/dist/lib/models/claudeAdapter.js +4 -0
- package/dist/lib/models/contract.d.ts +5 -2
- package/dist/lib/models/gateway.d.ts +3 -2
- package/dist/lib/models/gateway.js +8 -4
- package/dist/lib/models/gatewayAdapter.d.ts +7 -1
- package/dist/lib/models/gatewayAdapter.js +8 -0
- package/dist/lib/models/geminiAdapter.js +8 -2
- package/dist/lib/models/genaiMapping.d.ts +8 -4
- package/dist/lib/models/genaiMapping.js +23 -9
- package/dist/lib/models/gptAdapter.d.ts +4 -2
- package/dist/lib/models/gptAdapter.js +11 -4
- package/dist/lib/models/grokAdapter.d.ts +3 -1
- package/dist/lib/models/grokAdapter.js +11 -3
- package/dist/lib/models/kimiAdapter.d.ts +2 -1
- package/dist/lib/models/kimiAdapter.js +3 -3
- package/dist/lib/models/reasoning.d.ts +30 -2
- package/dist/lib/models/reasoning.js +56 -5
- package/dist/lib/onboarding.d.ts +135 -0
- package/dist/lib/onboarding.js +578 -0
- package/dist/lib/runtime/approvals.d.ts +2 -0
- package/dist/lib/runtime/approvals.js +4 -3
- package/dist/lib/runtime/credentials.d.ts +6 -0
- package/dist/lib/runtime/native/agentLoop.d.ts +5 -2
- package/dist/lib/runtime/native/agentLoop.js +2 -0
- package/dist/lib/runtime/native/checkpoint.d.ts +12 -5
- package/dist/lib/runtime/native/checkpoint.js +13 -19
- package/dist/lib/runtime/native/delegate.d.ts +82 -1
- package/dist/lib/runtime/native/delegate.js +157 -2
- package/dist/lib/runtime/native/interrupts.d.ts +88 -5
- package/dist/lib/runtime/native/interrupts.js +220 -30
- package/dist/lib/runtime/syndicateTurn.d.ts +4 -2
- package/dist/lib/runtime/syndicateTurn.js +324 -77
- package/dist/lib/runtime/turnControl.d.ts +41 -0
- package/dist/lib/session/transcript.d.ts +9 -0
- package/dist/lib/session/transcript.js +20 -1
- package/dist/lib/skills.d.ts +18 -0
- package/dist/lib/skills.js +73 -1
- package/dist/lib/storage/postgres/index.d.ts +6 -1
- package/dist/lib/storage/postgres/index.js +1 -1
- package/dist/lib/storage/postgres/taskQueue.d.ts +7 -3
- package/dist/lib/storage/postgres/taskQueue.js +10 -3
- package/dist/lib/syndicateSchema.d.ts +184 -9
- package/dist/lib/syndicateSchema.js +143 -29
- package/dist/lib/tools/auth.d.ts +13 -1
- package/dist/lib/tools/auth.js +10 -2
- package/dist/lib/tools/credentialHosts.d.ts +88 -0
- package/dist/lib/tools/credentialHosts.js +165 -0
- package/dist/lib/tools/credentialUses.d.ts +98 -0
- package/dist/lib/tools/credentialUses.js +120 -0
- package/dist/lib/tools/generateImageTool.js +2 -1
- package/dist/lib/tools/inspectImageTool.js +1 -1
- package/dist/lib/tools/mcpToolFactory.d.ts +29 -1
- package/dist/lib/tools/mcpToolFactory.js +72 -26
- package/dist/lib/tools/mcpTransports.d.ts +12 -0
- package/dist/lib/tools/mcpTransports.js +11 -0
- package/dist/lib/tools/oauthConsent.d.ts +18 -3
- package/dist/lib/tools/oauthConsent.js +57 -15
- package/dist/lib/tools/oauthDiscovery.d.ts +202 -0
- package/dist/lib/tools/oauthDiscovery.js +616 -0
- package/dist/lib/tools/oauthHosts.d.ts +2 -0
- package/dist/lib/tools/oauthHosts.js +2 -2
- package/dist/lib/tools/oauthTools.d.ts +54 -13
- package/dist/lib/tools/oauthTools.js +106 -23
- package/dist/lib/tools/openapiTools.d.ts +7 -0
- package/dist/lib/tools/openapiTools.js +49 -3
- package/dist/lib/tools/taskTools.d.ts +32 -2
- package/dist/lib/tools/taskTools.js +93 -33
- package/dist/lib/tools/xApiSearchTool.js +2 -2
- package/dist/lib/workflow/turn.d.ts +22 -3
- package/dist/lib/workflow/turn.js +151 -31
- package/dist/lib/workflow.d.ts +10 -7
- package/dist/lib/workflow.js +10 -7
- package/dist/scripts/a2a_server.d.ts +2 -2
- package/dist/scripts/a2a_server.js +19 -2
- package/dist/scripts/assistant_worker.d.ts +2 -0
- package/dist/scripts/assistant_worker.js +24 -5
- package/dist/scripts/doctor.d.ts +4 -2
- package/dist/scripts/doctor.js +6 -4
- package/dist/scripts/mcp_server.d.ts +12 -0
- package/dist/scripts/mcp_server.js +345 -0
- package/dist/scripts/setup.d.ts +25 -0
- package/dist/scripts/setup.js +181 -0
- package/dist/scripts/skills_install.d.ts +3 -1
- package/dist/scripts/skills_install.js +17 -2
- package/dist/scripts/syndicate_chat.js +12 -3
- package/dist/scripts/yaml_codemod.d.ts +12 -2
- package/dist/scripts/yaml_codemod.js +115 -33
- package/package.json +8 -2
- package/skills/README.md +11 -2
- package/skills/briefs/_shared.md +1 -1
- package/skills/briefs/melchizedek-models.md +4 -2
- package/skills/briefs/skills-readme.md +3 -1
- package/skills/melchizedek-models/SKILL.md +6 -2
- package/skills/melchizedek-onboard/SKILL.md +71 -0
- package/skills/melchizedek-onboard-cloud/SKILL.md +38 -0
- package/skills/melchizedek-onboard-keys/SKILL.md +47 -0
- package/skills/melchizedek-onboard-local/SKILL.md +41 -0
- package/skills/melchizedek-onboard-serve/SKILL.md +43 -0
|
@@ -365,7 +365,7 @@ orchestrator:
|
|
|
365
365
|
|
|
366
366
|
# ── reasoning ─────────────────────────────────────────────
|
|
367
367
|
# [optional] How hard the agent reasons, on any provider (ADR 0047):
|
|
368
|
-
# none | low | medium | high, or { budget_tokens: <int> }.
|
|
368
|
+
# none | low | medium | high | xhigh | max, or { budget_tokens: <int> }.
|
|
369
369
|
# The compiler sends each provider the field it reads:
|
|
370
370
|
# gemini-3 and later → thinkingConfig.thinkingLevel
|
|
371
371
|
# (none = MINIMAL, then LOW, MEDIUM, HIGH)
|
|
@@ -382,6 +382,10 @@ orchestrator:
|
|
|
382
382
|
# A budget is sent as-is to Gemini and to Claude 4.6 and earlier, and as
|
|
383
383
|
# the smallest level that covers it everywhere else. Unset, each provider
|
|
384
384
|
# keeps its default.
|
|
385
|
+
# xhigh and max (ADR 0117) reach the models that take them: both on later
|
|
386
|
+
# claude-*, max on kimi-k3 (xhigh rounds up to it), xhigh on gpt-5.2 and
|
|
387
|
+
# later and grok-4.7. Elsewhere the model's highest setting is sent and
|
|
388
|
+
# the span carries llm.reasoning.weakened; a gateway gets high.
|
|
385
389
|
# Cannot be combined with generateContentConfig.thinkingConfig or
|
|
386
390
|
# generateContentConfig.reasoningEffort, its deprecated v1 spelling.
|
|
387
391
|
reasoning: medium
|
|
@@ -422,6 +426,14 @@ orchestrator:
|
|
|
422
426
|
# answer: { type: string }
|
|
423
427
|
# required: [answer]
|
|
424
428
|
|
|
429
|
+
# ── tool_choice ───────────────────────────────────────────
|
|
430
|
+
# [optional] Which tools the model may call, on any provider (ADR 0117):
|
|
431
|
+
# auto (the default) | none | required (some tool)
|
|
432
|
+
# | { name: <tool> } (that one tool).
|
|
433
|
+
# A provider that rejects forcing a tool gets a weaker choice, and the
|
|
434
|
+
# span carries llm.tool_choice.weakened.
|
|
435
|
+
# tool_choice: auto
|
|
436
|
+
|
|
425
437
|
# ── model_overrides ───────────────────────────────────────
|
|
426
438
|
# [optional] A prompt nuance for one provider (ADR 0115). Keys are the
|
|
427
439
|
# providers a model id routes to: gemini, anthropic, openai, xai,
|
|
@@ -438,11 +450,11 @@ orchestrator:
|
|
|
438
450
|
# The v1 spelling: @google/genai's GenerateContentConfig, with
|
|
439
451
|
# outputSchema beside it. Still loads and behaves as before, with one
|
|
440
452
|
# deprecation line per file; `npx melchizedek-codemod <file>` rewrites
|
|
441
|
-
# it into sampling, output and
|
|
442
|
-
# spelling is a load error. Only
|
|
443
|
-
#
|
|
444
|
-
#
|
|
445
|
-
#
|
|
453
|
+
# it into sampling, output, reasoning and tool_choice. A v2 key beside
|
|
454
|
+
# its v1 spelling is a load error. Only toolConfig's VALIDATED mode and
|
|
455
|
+
# Gemini's dynamic budget (thinkingBudget: -1) have no v2 form. topK,
|
|
456
|
+
# seed, the penalties, candidateCount, safetySettings and
|
|
457
|
+
# includeThoughts reach no provider.
|
|
446
458
|
|
|
447
459
|
# ── Subagents ───────────────────────────────────────────────
|
|
448
460
|
# Each entry is an agent the orchestrator calls as a tool (DELEGATE),
|
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
#
|
|
24
24
|
# SHAPE: a single agent with an `outputSchema`, so the reply is JSON the
|
|
25
25
|
# schema describes, parseable without a second model call. No subagents and
|
|
26
|
-
# no tools
|
|
27
|
-
#
|
|
26
|
+
# no tools, by choice rather than by rule (an agent that delegates or calls
|
|
27
|
+
# tools may hold a schema too, ADR 0109): the document is the only input,
|
|
28
|
+
# and the extractor may not add anything the document does not say.
|
|
28
29
|
#
|
|
29
30
|
# REDACTION: card numbers, bank details, passwords and government IDs are
|
|
30
31
|
# never copied into the record, not even as a `references` entry; `redacted`
|
|
@@ -24,8 +24,11 @@
|
|
|
24
24
|
# `mcp_auth: { oauth2 }` (ADR 0112): uncomment the block on Systems
|
|
25
25
|
# below. client_credentials gives the server's own token on every
|
|
26
26
|
# request. authorization_code (each person's own token, granted
|
|
27
|
-
# through the consent pause)
|
|
28
|
-
#
|
|
27
|
+
# through the consent pause) works on Systems too: the first call
|
|
28
|
+
# for a person who has not connected pauses the turn with an
|
|
29
|
+
# authorization link, and their next message after the grant
|
|
30
|
+
# resumes Systems' call (ADR 0118). It also needs mcp_tools, the
|
|
31
|
+
# server's tool names, and an operator allowlist (ADR 0114).
|
|
29
32
|
# `npm run doctor` lists every tool that needs a grant.
|
|
30
33
|
# Loopback and private addresses are refused unless
|
|
31
34
|
# ALLOW_PRIVATE_MCP=true (local development).
|
package/dist/lib/a2a/app.d.ts
CHANGED
|
@@ -219,6 +219,18 @@ export interface A2AAppOptions {
|
|
|
219
219
|
* refused when loaded, when compiled, and at each call.
|
|
220
220
|
*/
|
|
221
221
|
oauthHosts?: Record<string, string[]>;
|
|
222
|
+
/**
|
|
223
|
+
* The credential host allowlist (ADR 0122): each static credential
|
|
224
|
+
* variable a YAML may send (`bearer_env`, `api_key.env`,
|
|
225
|
+
* `client_secret_env`) and the hosts its value may go to, e.g.
|
|
226
|
+
* `{ TRACKER_TOKEN: ['api.tracker.example.com'] }`. Wins over
|
|
227
|
+
* MELCHIZEDEK_CREDENTIAL_HOSTS; it is set for this process. With neither,
|
|
228
|
+
* a credential goes to the host the YAML names, as before. With one, it is
|
|
229
|
+
* the whole list: a syndicate that sends a variable it does not bind, or
|
|
230
|
+
* to a host outside that variable's, is refused when loaded, when
|
|
231
|
+
* compiled, and at each call.
|
|
232
|
+
*/
|
|
233
|
+
credentialHosts?: Record<string, string[]>;
|
|
222
234
|
toolCredentials?: ToolCredentials & {
|
|
223
235
|
/** Callback requests per window per client IP. Default 30 per 15 minutes. */
|
|
224
236
|
callbackLimit?: {
|
package/dist/lib/a2a/app.js
CHANGED
|
@@ -46,6 +46,7 @@ import { duplicateInterfacesForLegacy } from '@a2a-js/sdk/compat/v0_3';
|
|
|
46
46
|
import { agentCardHandler, jsonRpcHandler, restHandler } from '@a2a-js/sdk/server/express';
|
|
47
47
|
import { InProcessSessionService } from '../runtime/sessions.js';
|
|
48
48
|
import { runtimeSetting } from '../runtime/runtimeFlag.js';
|
|
49
|
+
import { refuseChatGptSignInOnServedSurface } from '../chatgpt/state.js';
|
|
49
50
|
import { geminiAdapterSetting } from '../models/adapterResolver.js';
|
|
50
51
|
import { loadSyndicate, loadSyndicateFromRegistry, nestedLoader } from '../loadSyndicate.js';
|
|
51
52
|
import { providerForModel, resolveModel } from '../models/registry.js';
|
|
@@ -64,6 +65,8 @@ import { HEADER_VALUE_PATTERN, SCOPE_KEY_PATTERN } from './identity.js';
|
|
|
64
65
|
import { createMetrics } from '../observability/metrics.js';
|
|
65
66
|
import { ConsentError } from '../tools/oauthConsent.js';
|
|
66
67
|
import { setOAuthHosts } from '../tools/oauthHosts.js';
|
|
68
|
+
import { setCredentialHosts } from '../tools/credentialHosts.js';
|
|
69
|
+
import { syndicateCredentialHostProblems } from '../tools/credentialUses.js';
|
|
67
70
|
import { syndicateOAuthHostProblems } from '../tools/oauthTools.js';
|
|
68
71
|
const SURFACE_HEADERS = [
|
|
69
72
|
['x-surface', 'name'],
|
|
@@ -262,6 +265,8 @@ export async function createA2AApp(options) {
|
|
|
262
265
|
// A deployment still configured for the removed adk runtime fails here, naming 1.0.0 (ADR 0107).
|
|
263
266
|
runtimeSetting();
|
|
264
267
|
geminiAdapterSetting();
|
|
268
|
+
// Sign in with ChatGPT is local only (ADR 0126): a served app never runs OpenAI ids on it.
|
|
269
|
+
refuseChatGptSignInOnServedSurface('The A2A server');
|
|
265
270
|
const log = options.log ?? ((m) => console.log(`[A2A] ${m}`));
|
|
266
271
|
const warn = options.warn ?? ((m) => console.warn(`[A2A] ⚠ ${m}`));
|
|
267
272
|
const bindings = options.bindings ?? (() => ({
|
|
@@ -280,21 +285,30 @@ export async function createA2AApp(options) {
|
|
|
280
285
|
// ── The OAuth host allowlist (ADR 0114) ────────────────────────────────────
|
|
281
286
|
if (options.oauthHosts)
|
|
282
287
|
setOAuthHosts(options.oauthHosts);
|
|
283
|
-
|
|
288
|
+
// ── The credential host allowlist (ADR 0122) ───────────────────────────────
|
|
289
|
+
if (options.credentialHosts)
|
|
290
|
+
setCredentialHosts(options.credentialHosts);
|
|
291
|
+
/**
|
|
292
|
+
* A served syndicate whose OAuth grant names a provider or host outside the
|
|
293
|
+
* OAuth allowlist, or that sends a static credential the credential
|
|
294
|
+
* allowlist does not bind to its host, is refused at load.
|
|
295
|
+
*/
|
|
284
296
|
const checkOAuthHosts = (cfg, label) => {
|
|
285
297
|
const nested = nestedLoader(cfg, (ref) => loadSyndicate(ref, { bindings: bindings() }));
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
});
|
|
298
|
+
const load = (ref) => {
|
|
299
|
+
try {
|
|
300
|
+
return nested(ref);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return {}; // A nested file that cannot load fails its own compile.
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const problems = syndicateOAuthHostProblems([cfg], { load });
|
|
296
307
|
if (problems.length)
|
|
297
308
|
throw new Error(`${label}: refusing an OAuth grant the operator's host allowlist does not permit:\n - ${problems.join('\n - ')}`);
|
|
309
|
+
const credentials = syndicateCredentialHostProblems([cfg], { load });
|
|
310
|
+
if (credentials.length)
|
|
311
|
+
throw new Error(`${label}: refusing a credential the operator's credential host allowlist does not permit:\n - ${credentials.join('\n - ')}`);
|
|
298
312
|
return cfg;
|
|
299
313
|
};
|
|
300
314
|
// ── The default syndicate ──────────────────────────────────────────────────
|
package/dist/lib/a2a/executor.js
CHANGED
|
@@ -22,7 +22,7 @@ import { turnLockKey } from './turnLock.js';
|
|
|
22
22
|
import { ingestTurnMemory, runSyndicateTurn } from '../runtime/syndicateTurn.js';
|
|
23
23
|
import { approvalResponsePart, describeApproval, pendingApproval } from '../runtime/approvals.js';
|
|
24
24
|
import { declaresApprovals } from '../compile.js';
|
|
25
|
-
import { delegatedPauses } from '../runtime/native/interrupts.js';
|
|
25
|
+
import { deepestPause, delegatedPauses, routePause } from '../runtime/native/interrupts.js';
|
|
26
26
|
import { isWorkflowSyndicate } from '../workflow.js';
|
|
27
27
|
import { describeInput } from '../runtime/syndicateTurn.js';
|
|
28
28
|
import { describeConsent } from '../runtime/credentials.js';
|
|
@@ -323,7 +323,8 @@ function publishApprovalRequest(eventBus, taskId, contextId, pending) {
|
|
|
323
323
|
* naming the provider, with a data part carrying the authorization URL and
|
|
324
324
|
* its state nonce. The person opens the URL; the server's callback stores
|
|
325
325
|
* the grant; the next message on the conversation resumes the call. The
|
|
326
|
-
* part carries no token, code or verifier.
|
|
326
|
+
* part carries no token, code or verifier. For a call inside a delegated
|
|
327
|
+
* subagent it carries the agent path too (ADR 0118).
|
|
327
328
|
*/
|
|
328
329
|
function publishConsentRequest(eventBus, taskId, contextId, pending) {
|
|
329
330
|
const message = statusMessage(taskId, contextId, `Authorization needed: ${describeConsent(pending)}. Open ${pending.authUri} to authorize, then send any message on this conversation to continue.`);
|
|
@@ -338,6 +339,8 @@ function publishConsentRequest(eventBus, taskId, contextId, pending) {
|
|
|
338
339
|
authorization_url: pending.authUri,
|
|
339
340
|
state: pending.state,
|
|
340
341
|
scopes: pending.scopes,
|
|
342
|
+
// A call paused inside a delegated subagent: the agents from the turn's own down to the one that asked (ADR 0118).
|
|
343
|
+
...(pending.path ? { path: pending.path } : {}),
|
|
341
344
|
},
|
|
342
345
|
},
|
|
343
346
|
metadata: undefined,
|
|
@@ -594,13 +597,18 @@ export class SyndicateExecutor {
|
|
|
594
597
|
// A call gated inside a delegated subagent waits below an open call
|
|
595
598
|
// (ADR 0110), wherever the gate is declared, so a syndicate that
|
|
596
599
|
// delegates is read too.
|
|
597
|
-
|
|
598
|
-
|
|
600
|
+
// A workflow's node that is a nested workflow, and a dispatch route that is one, pause on their walk's gates
|
|
601
|
+
// wherever those are declared (ADR 0119), so every workflow, and every syndicate that delegates or routes, is read.
|
|
602
|
+
const workflow = isWorkflowSyndicate(config);
|
|
603
|
+
const delegates = !workflow && !!config.subagents?.length;
|
|
604
|
+
if (declaresApprovals(config) || delegates || workflow) {
|
|
599
605
|
const session = await this.sessions.get({ appName, userId, sessionId: contextId });
|
|
600
606
|
// The stored Event JSON (ADR 0052), read as TurnEvents.
|
|
601
607
|
const events = session?.events ?? [];
|
|
602
|
-
const
|
|
603
|
-
|
|
608
|
+
const key = { sessions: this.sessions, userId, sessionId: contextId, appName };
|
|
609
|
+
const own = declaresApprovals(config) || workflow ? pendingApproval(events) : undefined;
|
|
610
|
+
const pending = (own && workflow ? await deepestPause(key, own) : own) ??
|
|
611
|
+
(delegates ? ((await routePause(key, events))?.approval ?? (await delegatedPauses(key, events))[0]?.approval) : undefined);
|
|
604
612
|
if (pending) {
|
|
605
613
|
const answer = approvalAnswer(rawParts, pending);
|
|
606
614
|
if (!answer) {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* MELCHIZEDEK_CREDENTIAL_KEY the key that seals each user's tokens (ADR 0072)
|
|
14
14
|
* OAUTH_REDIRECT_URI the consent callback, as registered at every provider (ADR 0085)
|
|
15
15
|
* MELCHIZEDEK_OAUTH_HOSTS which hosts each provider's tokens may go to (ADR 0114)
|
|
16
|
+
* MELCHIZEDEK_CREDENTIAL_HOSTS which hosts each static credential variable may go to (ADR 0122)
|
|
16
17
|
* the client id and secret variables each YAML grant names
|
|
17
18
|
*
|
|
18
19
|
* The consent clients are built from the syndicates the server serves from
|
|
@@ -54,6 +55,19 @@ export declare function grantHostProblems(use: OAuthGrantUse, env?: NodeJS.Proce
|
|
|
54
55
|
* server refuses to start on the ones that would make it unsafe.
|
|
55
56
|
*/
|
|
56
57
|
export declare function oauthEnvProblems(grants: readonly OAuthGrantUse[], env?: NodeJS.ProcessEnv): string[];
|
|
58
|
+
/**
|
|
59
|
+
* The warning for credential variables no allowlist binds (ADR 0122), or
|
|
60
|
+
* null: with no credential host allowlist, each variable in `envs` (the
|
|
61
|
+
* ones the served YAML sends) still goes to whatever host the YAML names.
|
|
62
|
+
* Names only.
|
|
63
|
+
*/
|
|
64
|
+
export declare function unboundCredentialWarning(envs: readonly string[], env?: NodeJS.ProcessEnv): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* What is wrong with the credential host allowlist for these uses, one line
|
|
67
|
+
* each, names and hosts only: a malformed allowlist, or each use it refuses
|
|
68
|
+
* (lib/tools/credentialUses.ts). Empty when none is configured.
|
|
69
|
+
*/
|
|
70
|
+
export declare function credentialHostEnvProblems(configs: readonly SyndicateLike[], env?: NodeJS.ProcessEnv, load?: (ref: string) => SyndicateLike): string[];
|
|
57
71
|
/** The parts of a syndicate config the setup reads. */
|
|
58
72
|
type SyndicateLike = Parameters<typeof oauthClientsFor>[0][number];
|
|
59
73
|
export interface OAuthServerSetupOptions {
|
|
@@ -76,6 +90,8 @@ export interface OAuthServerSetup {
|
|
|
76
90
|
toolCredentials?: ToolCredentials;
|
|
77
91
|
/** One line for the server's banner: key id, row store, consent path, providers, allowlist. No value. */
|
|
78
92
|
summary: string;
|
|
93
|
+
/** One line for the server's banner: the credential host allowlist (ADR 0122), variable names only. */
|
|
94
|
+
credentialSummary: string;
|
|
79
95
|
/** What works less than the YAML asks, names only. */
|
|
80
96
|
warnings: string[];
|
|
81
97
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* MELCHIZEDEK_CREDENTIAL_KEY the key that seals each user's tokens (ADR 0072)
|
|
14
14
|
* OAUTH_REDIRECT_URI the consent callback, as registered at every provider (ADR 0085)
|
|
15
15
|
* MELCHIZEDEK_OAUTH_HOSTS which hosts each provider's tokens may go to (ADR 0114)
|
|
16
|
+
* MELCHIZEDEK_CREDENTIAL_HOSTS which hosts each static credential variable may go to (ADR 0122)
|
|
16
17
|
* the client id and secret variables each YAML grant names
|
|
17
18
|
*
|
|
18
19
|
* The consent clients are built from the syndicates the server serves from
|
|
@@ -25,8 +26,11 @@
|
|
|
25
26
|
import { CREDENTIAL_KEY_ENV, credentialCipherFromEnv } from '../tools/credentialCipher.js';
|
|
26
27
|
import { credentialStore, memoryCredentialRows } from '../tools/credentialStore.js';
|
|
27
28
|
import { oauthConsent } from '../tools/oauthConsent.js';
|
|
29
|
+
import { dynamicOAuthClient, oauthClientRegistry } from '../tools/oauthDiscovery.js';
|
|
28
30
|
import { OAUTH_HOSTS_ENV, oauthHosts } from '../tools/oauthHosts.js';
|
|
29
|
-
import { oauthClientsFor, oauthGrantHostProblems, oauthRefreshProviders, syndicateOAuthGrants, syndicateOAuthHostProblems } from '../tools/oauthTools.js';
|
|
31
|
+
import { dynamicOAuthGrantsFor, oauthClientsFor, oauthGrantHostProblems, oauthRefreshProviders, syndicateOAuthGrants, syndicateOAuthHostProblems } from '../tools/oauthTools.js';
|
|
32
|
+
import { CREDENTIAL_HOSTS_ENV, credentialHosts } from '../tools/credentialHosts.js';
|
|
33
|
+
import { syndicateCredentialHostProblems, syndicateCredentialUses, unboundCredentialEnvs } from '../tools/credentialUses.js';
|
|
30
34
|
/** The consent callback's URL, exactly as registered at every provider. */
|
|
31
35
|
export const OAUTH_REDIRECT_URI_ENV = 'OAUTH_REDIRECT_URI';
|
|
32
36
|
/** Who may complete a consent callback: `required` (the browser carries the user's identity) or `state` (the nonce alone). */
|
|
@@ -138,6 +142,42 @@ export function oauthEnvProblems(grants, env = process.env) {
|
|
|
138
142
|
}
|
|
139
143
|
return problems;
|
|
140
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* The warning for credential variables no allowlist binds (ADR 0122), or
|
|
147
|
+
* null: with no credential host allowlist, each variable in `envs` (the
|
|
148
|
+
* ones the served YAML sends) still goes to whatever host the YAML names.
|
|
149
|
+
* Names only.
|
|
150
|
+
*/
|
|
151
|
+
export function unboundCredentialWarning(envs, env = process.env) {
|
|
152
|
+
let allowlist;
|
|
153
|
+
try {
|
|
154
|
+
allowlist = credentialHosts(env);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null; // A malformed allowlist is a problem, reported as one.
|
|
158
|
+
}
|
|
159
|
+
if (allowlist)
|
|
160
|
+
return null; // Configured: an unbound variable is refused, not warned about.
|
|
161
|
+
const unbound = [...new Set(envs)].sort();
|
|
162
|
+
if (!unbound.length)
|
|
163
|
+
return null;
|
|
164
|
+
return `${unbound.join(', ')} ${unbound.length === 1 ? 'is' : 'are'} sent to whatever host the YAML names: no ${CREDENTIAL_HOSTS_ENV} binds ${unbound.length === 1 ? 'it' : 'them'} to ${unbound.length === 1 ? 'its' : 'their'} hosts`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* What is wrong with the credential host allowlist for these uses, one line
|
|
168
|
+
* each, names and hosts only: a malformed allowlist, or each use it refuses
|
|
169
|
+
* (lib/tools/credentialUses.ts). Empty when none is configured.
|
|
170
|
+
*/
|
|
171
|
+
export function credentialHostEnvProblems(configs, env = process.env, load) {
|
|
172
|
+
let allowlist;
|
|
173
|
+
try {
|
|
174
|
+
allowlist = credentialHosts(env);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
return [err instanceof Error ? err.message : `${CREDENTIAL_HOSTS_ENV} is malformed`];
|
|
178
|
+
}
|
|
179
|
+
return syndicateCredentialHostProblems(configs, { allowlist: allowlist ?? null, ...(load ? { load } : {}) });
|
|
180
|
+
}
|
|
141
181
|
/**
|
|
142
182
|
* The credential store and consent step from the environment. Throws, naming
|
|
143
183
|
* variables only, when the configuration would be unsafe or cannot work: a
|
|
@@ -163,19 +203,47 @@ export function oauthServerSetup(options) {
|
|
|
163
203
|
const refused = syndicateOAuthHostProblems(options.configs, { ...(options.load ? { load: options.load } : {}), allowlist: allowlist ?? null });
|
|
164
204
|
if (refused.length)
|
|
165
205
|
throw new Error(`refusing an OAuth grant the operator's host allowlist does not permit:\n - ${refused.join('\n - ')}`);
|
|
206
|
+
// The credential host allowlist (ADR 0122): malformed throws here, at boot; a served file it refuses too.
|
|
207
|
+
const credentialList = credentialHosts(env);
|
|
208
|
+
const credentialRefused = syndicateCredentialHostProblems(options.configs, { ...(options.load ? { load: options.load } : {}), allowlist: credentialList ?? null });
|
|
209
|
+
if (credentialRefused.length)
|
|
210
|
+
throw new Error(`refusing a credential the operator's credential host allowlist does not permit:\n - ${credentialRefused.join('\n - ')}`);
|
|
211
|
+
const uses = syndicateCredentialUses(options.configs, options.load);
|
|
212
|
+
const unboundWarning = unboundCredentialWarning(uses.map((u) => u.env), env);
|
|
213
|
+
if (unboundWarning)
|
|
214
|
+
warnings.push(`${unboundWarning.charAt(0).toUpperCase()}${unboundWarning.slice(1)}.`);
|
|
215
|
+
const credentialSummary = credentialList
|
|
216
|
+
? `bound: ${Object.keys(credentialList).join(', ')} (${CREDENTIAL_HOSTS_ENV})`
|
|
217
|
+
: uses.length
|
|
218
|
+
? `no allowlist (${CREDENTIAL_HOSTS_ENV}): ${unboundCredentialEnvs(uses, undefined).join(', ')} unbound`
|
|
219
|
+
: `no allowlist (${CREDENTIAL_HOSTS_ENV}); no served file sends a static credential`;
|
|
166
220
|
if (!cipher) {
|
|
167
221
|
if (redirect)
|
|
168
222
|
throw new Error(`${OAUTH_REDIRECT_URI_ENV} is set but ${CREDENTIAL_KEY_ENV} is not: the consent step has nowhere to keep a grant. Set ${CREDENTIAL_KEY_ENV} (openssl rand -base64 32).`);
|
|
169
223
|
if (authCode.length)
|
|
170
224
|
warnings.push(`authorization_code grants (${authCode.join(', ')}) are declared but ${CREDENTIAL_KEY_ENV} is not set: their tools answer unavailable.`);
|
|
171
|
-
return { summary: `off (${CREDENTIAL_KEY_ENV} unset) · ${hostsLabel}`, warnings };
|
|
225
|
+
return { summary: `off (${CREDENTIAL_KEY_ENV} unset) · ${hostsLabel}`, credentialSummary, warnings };
|
|
172
226
|
}
|
|
173
227
|
const clients = oauthClientsFor(options.configs, { env, ...(options.load ? { load: options.load } : {}), allowlist: allowlist ?? null });
|
|
174
|
-
const
|
|
228
|
+
const rows = options.rows ?? memoryCredentialRows();
|
|
229
|
+
// A grant with client_registration: dynamic (ADR 0124): its client is
|
|
230
|
+
// discovered and registered when first needed, for the configured redirect
|
|
231
|
+
// URI, and kept sealed beside the users' tokens. Without a redirect URI it
|
|
232
|
+
// cannot register, and is named in the warning below.
|
|
233
|
+
const dynamicGrants = dynamicOAuthGrantsFor(options.configs, { env, ...(options.load ? { load: options.load } : {}), allowlist: allowlist ?? null });
|
|
234
|
+
const registry = oauthClientRegistry({ rows, cipher });
|
|
235
|
+
const dynamicClients = redirect
|
|
236
|
+
? Object.fromEntries(Object.entries(dynamicGrants).map(([name, grant]) => [
|
|
237
|
+
name,
|
|
238
|
+
dynamicOAuthClient(grant, { redirectUri: redirect, registry, ...(options.allowPrivate ? { allowPrivate: true } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) }),
|
|
239
|
+
]))
|
|
240
|
+
: {};
|
|
241
|
+
const allClients = { ...clients, ...dynamicClients };
|
|
242
|
+
const providers = [...Object.keys(clients), ...Object.keys(dynamicGrants)];
|
|
175
243
|
const store = credentialStore({
|
|
176
|
-
rows
|
|
244
|
+
rows,
|
|
177
245
|
cipher,
|
|
178
|
-
providers: oauthRefreshProviders(
|
|
246
|
+
providers: oauthRefreshProviders(allClients, { ...(options.allowPrivate ? { allowPrivate: true } : {}), ...(options.fetch ? { fetch: options.fetch } : {}) }),
|
|
179
247
|
...(options.audit ? { audit: options.audit } : {}),
|
|
180
248
|
});
|
|
181
249
|
if (!options.rows)
|
|
@@ -184,14 +252,14 @@ export function oauthServerSetup(options) {
|
|
|
184
252
|
if (!redirect) {
|
|
185
253
|
if (providers.length)
|
|
186
254
|
warnings.push(`${OAUTH_REDIRECT_URI_ENV} is not set: a user who has not connected ${providers.join(', ')} cannot be asked to.`);
|
|
187
|
-
return { toolCredentials: { store }, summary: `sealed (key ${cipher.keyId}) in ${where}; no consent step · ${hostsLabel}`, warnings };
|
|
255
|
+
return { toolCredentials: { store }, summary: `sealed (key ${cipher.keyId}) in ${where}; no consent step · ${hostsLabel}`, credentialSummary, warnings };
|
|
188
256
|
}
|
|
189
257
|
if (!providers.length) {
|
|
190
258
|
warnings.push(`${OAUTH_REDIRECT_URI_ENV} is set but no served syndicate declares an authorization_code grant: no consent callback is mounted.`);
|
|
191
|
-
return { toolCredentials: { store }, summary: `sealed (key ${cipher.keyId}) in ${where}; no consent step · ${hostsLabel}`, warnings };
|
|
259
|
+
return { toolCredentials: { store }, summary: `sealed (key ${cipher.keyId}) in ${where}; no consent step · ${hostsLabel}`, credentialSummary, warnings };
|
|
192
260
|
}
|
|
193
261
|
const consent = oauthConsent({
|
|
194
|
-
providers:
|
|
262
|
+
providers: allClients,
|
|
195
263
|
redirectUri: redirect,
|
|
196
264
|
credentials: store,
|
|
197
265
|
...(options.audit ? { audit: options.audit } : {}),
|
|
@@ -200,6 +268,7 @@ export function oauthServerSetup(options) {
|
|
|
200
268
|
return {
|
|
201
269
|
toolCredentials: { store, consent },
|
|
202
270
|
summary: `sealed (key ${cipher.keyId}) in ${where}; consent at ${new URL(redirect).pathname} for ${providers.join(', ')} · ${hostsLabel}`,
|
|
271
|
+
credentialSummary,
|
|
203
272
|
warnings,
|
|
204
273
|
};
|
|
205
274
|
}
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
* Gemini's dialect (`generateContentConfig.temperature`, `outputSchema`),
|
|
8
8
|
* which is what the engine still reads (lib/runtime/native/request.ts
|
|
9
9
|
* buildModelRequest). YAML v2 spells them provider-neutrally (`sampling`,
|
|
10
|
-
* `output`) and adds per-provider
|
|
10
|
+
* `output`, and `tool_choice` from ADR 0117) and adds per-provider
|
|
11
|
+
* instruction overrides (`model_overrides`).
|
|
11
12
|
* This module is the one place the two spellings meet:
|
|
12
13
|
*
|
|
13
14
|
* - `V2_SPELLINGS`: each v2 key path and the v1 path it replaces. The
|
|
14
15
|
* loader (lib/syndicateSchema.ts crossFieldProblems) refuses a file that
|
|
15
16
|
* sets both; `toEngineAgent` refuses a config built in code that does.
|
|
16
|
-
* - `toEngineAgent`: folds `sampling` and `
|
|
17
|
+
* - `toEngineAgent`: folds `sampling`, `output` and `tool_choice` into
|
|
18
|
+
* the engine form.
|
|
17
19
|
* validateSyndicateConfig applies it to every agent it returns, so every
|
|
18
20
|
* loader hands downstream the engine form; lib/compile.ts applies it
|
|
19
21
|
* again (idempotent) for configs built in code.
|
|
@@ -27,13 +29,21 @@
|
|
|
27
29
|
import type { AgentYamlConfig } from './loadSyndicate.ts';
|
|
28
30
|
/** One v2 key and the v1 (engine) path it replaces. Both paths are relative to the agent. */
|
|
29
31
|
export interface V2Spelling {
|
|
30
|
-
readonly v2: readonly [
|
|
32
|
+
readonly v2: readonly string[];
|
|
31
33
|
readonly v1: readonly string[];
|
|
34
|
+
/** The engine value for the v2 value, when the two are spelled differently. */
|
|
35
|
+
readonly toV1?: (value: unknown) => unknown;
|
|
32
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* `tool_choice` as Gemini's function-calling config, which the engine reads
|
|
39
|
+
* (lib/models/genaiMapping.ts toolChoiceOf): `auto` is AUTO, `none` NONE,
|
|
40
|
+
* `required` ANY, and `{ name }` ANY with that one name allowed (ADR 0117).
|
|
41
|
+
*/
|
|
42
|
+
export declare function functionCallingConfigOf(choice: unknown): Record<string, unknown>;
|
|
33
43
|
/** Every v2 key with a v1 equivalent; the mapper writes the v1 path. */
|
|
34
44
|
export declare const V2_SPELLINGS: readonly V2Spelling[];
|
|
35
45
|
/** The v2 agent keys, which an inline agent alone may carry (not a yaml_reference or a2a_agent_url subagent). */
|
|
36
|
-
export declare const V2_AGENT_KEYS: readonly ['sampling', 'output', 'model_overrides'];
|
|
46
|
+
export declare const V2_AGENT_KEYS: readonly ['sampling', 'output', 'tool_choice', 'model_overrides'];
|
|
37
47
|
/** The v1 spellings ADR 0115 deprecates, as agent keys. */
|
|
38
48
|
export declare const V1_AGENT_KEYS: readonly ['generateContentConfig', 'outputSchema'];
|
|
39
49
|
/** The v2/v1 pairs an agent sets both of: each is a conflict. */
|
|
@@ -41,10 +51,10 @@ export declare function conflictsOf(agent: unknown): V2Spelling[];
|
|
|
41
51
|
/** The loader's message for one conflict, reported against the v2 key path. */
|
|
42
52
|
export declare function conflictMessage(s: V2Spelling): string;
|
|
43
53
|
/**
|
|
44
|
-
* An agent in the engine form: `sampling` and `
|
|
45
|
-
* `generateContentConfig` and `outputSchema` (the table above) and
|
|
46
|
-
* `model_overrides` kept (lib/compile.ts applies it). The same
|
|
47
|
-
* the agent has
|
|
54
|
+
* An agent in the engine form: `sampling`, `output` and `tool_choice` folded
|
|
55
|
+
* into `generateContentConfig` and `outputSchema` (the table above) and
|
|
56
|
+
* removed; `model_overrides` kept (lib/compile.ts applies it). The same
|
|
57
|
+
* object when the agent has none of the three, so it is idempotent. Throws
|
|
48
58
|
* when a v2 key and the v1 key it replaces are both set.
|
|
49
59
|
*/
|
|
50
60
|
export declare function toEngineAgent<T extends object>(agent: T): T;
|
package/dist/lib/agentDialect.js
CHANGED
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
* Gemini's dialect (`generateContentConfig.temperature`, `outputSchema`),
|
|
8
8
|
* which is what the engine still reads (lib/runtime/native/request.ts
|
|
9
9
|
* buildModelRequest). YAML v2 spells them provider-neutrally (`sampling`,
|
|
10
|
-
* `output`) and adds per-provider
|
|
10
|
+
* `output`, and `tool_choice` from ADR 0117) and adds per-provider
|
|
11
|
+
* instruction overrides (`model_overrides`).
|
|
11
12
|
* This module is the one place the two spellings meet:
|
|
12
13
|
*
|
|
13
14
|
* - `V2_SPELLINGS`: each v2 key path and the v1 path it replaces. The
|
|
14
15
|
* loader (lib/syndicateSchema.ts crossFieldProblems) refuses a file that
|
|
15
16
|
* sets both; `toEngineAgent` refuses a config built in code that does.
|
|
16
|
-
* - `toEngineAgent`: folds `sampling` and `
|
|
17
|
+
* - `toEngineAgent`: folds `sampling`, `output` and `tool_choice` into
|
|
18
|
+
* the engine form.
|
|
17
19
|
* validateSyndicateConfig applies it to every agent it returns, so every
|
|
18
20
|
* loader hands downstream the engine form; lib/compile.ts applies it
|
|
19
21
|
* again (idempotent) for configs built in code.
|
|
@@ -26,6 +28,20 @@
|
|
|
26
28
|
*/
|
|
27
29
|
import { providerForModel } from './models/providerMap.js';
|
|
28
30
|
const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
31
|
+
/**
|
|
32
|
+
* `tool_choice` as Gemini's function-calling config, which the engine reads
|
|
33
|
+
* (lib/models/genaiMapping.ts toolChoiceOf): `auto` is AUTO, `none` NONE,
|
|
34
|
+
* `required` ANY, and `{ name }` ANY with that one name allowed (ADR 0117).
|
|
35
|
+
*/
|
|
36
|
+
export function functionCallingConfigOf(choice) {
|
|
37
|
+
if (choice === 'none')
|
|
38
|
+
return { mode: 'NONE' };
|
|
39
|
+
if (choice === 'required')
|
|
40
|
+
return { mode: 'ANY' };
|
|
41
|
+
if (isObj(choice) && typeof choice.name === 'string')
|
|
42
|
+
return { mode: 'ANY', allowedFunctionNames: [choice.name] };
|
|
43
|
+
return { mode: 'AUTO' };
|
|
44
|
+
}
|
|
29
45
|
/** Every v2 key with a v1 equivalent; the mapper writes the v1 path. */
|
|
30
46
|
export const V2_SPELLINGS = [
|
|
31
47
|
{ v2: ['sampling', 'temperature'], v1: ['generateContentConfig', 'temperature'] },
|
|
@@ -34,9 +50,10 @@ export const V2_SPELLINGS = [
|
|
|
34
50
|
{ v2: ['sampling', 'stop'], v1: ['generateContentConfig', 'stopSequences'] },
|
|
35
51
|
{ v2: ['output', 'schema'], v1: ['outputSchema'] },
|
|
36
52
|
{ v2: ['output', 'mime'], v1: ['generateContentConfig', 'responseMimeType'] },
|
|
53
|
+
{ v2: ['tool_choice'], v1: ['generateContentConfig', 'toolConfig', 'functionCallingConfig'], toV1: functionCallingConfigOf },
|
|
37
54
|
];
|
|
38
55
|
/** The v2 agent keys, which an inline agent alone may carry (not a yaml_reference or a2a_agent_url subagent). */
|
|
39
|
-
export const V2_AGENT_KEYS = ['sampling', 'output', 'model_overrides'];
|
|
56
|
+
export const V2_AGENT_KEYS = ['sampling', 'output', 'tool_choice', 'model_overrides'];
|
|
40
57
|
/** The v1 spellings ADR 0115 deprecates, as agent keys. */
|
|
41
58
|
export const V1_AGENT_KEYS = ['generateContentConfig', 'outputSchema'];
|
|
42
59
|
/** The value at `path` under `root`, or undefined. */
|
|
@@ -58,37 +75,37 @@ export function conflictMessage(s) {
|
|
|
58
75
|
return `cannot be combined with ${s.v1.join('.')}; ${s.v2.join('.')} replaces it, so keep one (ADR 0115)`;
|
|
59
76
|
}
|
|
60
77
|
/**
|
|
61
|
-
* An agent in the engine form: `sampling` and `
|
|
62
|
-
* `generateContentConfig` and `outputSchema` (the table above) and
|
|
63
|
-
* `model_overrides` kept (lib/compile.ts applies it). The same
|
|
64
|
-
* the agent has
|
|
78
|
+
* An agent in the engine form: `sampling`, `output` and `tool_choice` folded
|
|
79
|
+
* into `generateContentConfig` and `outputSchema` (the table above) and
|
|
80
|
+
* removed; `model_overrides` kept (lib/compile.ts applies it). The same
|
|
81
|
+
* object when the agent has none of the three, so it is idempotent. Throws
|
|
65
82
|
* when a v2 key and the v1 key it replaces are both set.
|
|
66
83
|
*/
|
|
67
84
|
export function toEngineAgent(agent) {
|
|
68
85
|
const a = agent;
|
|
69
|
-
if (a.sampling === undefined && a.output === undefined)
|
|
86
|
+
if (a.sampling === undefined && a.output === undefined && a.tool_choice === undefined)
|
|
70
87
|
return agent;
|
|
71
88
|
const clash = conflictsOf(a);
|
|
72
89
|
if (clash.length) {
|
|
73
90
|
const name = typeof a.name === 'string' ? a.name : 'agent';
|
|
74
91
|
throw new Error(`${name}: ${clash.map((s) => `${s.v2.join('.')} cannot be combined with ${s.v1.join('.')}`).join('; ')}; the v2 key replaces it, so keep one (ADR 0115)`);
|
|
75
92
|
}
|
|
76
|
-
const { sampling: _sampling, output: _output, ...rest } = a;
|
|
93
|
+
const { sampling: _sampling, output: _output, tool_choice: _toolChoice, ...rest } = a;
|
|
77
94
|
const out = { ...rest };
|
|
78
|
-
let gcc;
|
|
79
95
|
for (const s of V2_SPELLINGS) {
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
continue;
|
|
83
|
-
if (s.v1.length === 1) {
|
|
84
|
-
out[s.v1[0]] = value;
|
|
96
|
+
const raw = at(a, s.v2);
|
|
97
|
+
if (raw === undefined)
|
|
85
98
|
continue;
|
|
99
|
+
const value = s.toV1 ? s.toV1(raw) : raw;
|
|
100
|
+
// Copy each object on the v1 path, so the caller's config is never written to.
|
|
101
|
+
let parent = out;
|
|
102
|
+
for (const seg of s.v1.slice(0, -1)) {
|
|
103
|
+
const next = { ...(isObj(parent[seg]) ? parent[seg] : {}) };
|
|
104
|
+
parent[seg] = next;
|
|
105
|
+
parent = next;
|
|
86
106
|
}
|
|
87
|
-
|
|
88
|
-
gcc[s.v1[1]] = value;
|
|
107
|
+
parent[s.v1[s.v1.length - 1]] = value;
|
|
89
108
|
}
|
|
90
|
-
if (gcc)
|
|
91
|
-
out.generateContentConfig = gcc;
|
|
92
109
|
return out;
|
|
93
110
|
}
|
|
94
111
|
/**
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/chatgpt/adapter.ts — GPT on the person's ChatGPT plan, through Sign in
|
|
3
|
+
* with ChatGPT, on their own machine only (ADR 0126).
|
|
4
|
+
*
|
|
5
|
+
* It is GptAdapter (the Responses API) with a different credential and the
|
|
6
|
+
* request shape OpenAI documents for plan usage
|
|
7
|
+
* (developers.openai.com/siwc/token-sharing-open-source/models-and-inference
|
|
8
|
+
* and …/preview-limitations):
|
|
9
|
+
* - always `POST https://api.openai.com/v1/responses`, never a proxy or a
|
|
10
|
+
* platform, with the stored access token as the bearer, refreshed as it
|
|
11
|
+
* nears expiry (lib/chatgpt/oauth.ts freshAccessToken), and no
|
|
12
|
+
* organization or project header;
|
|
13
|
+
* - `stream: true` and `store: false` on every request; a call that did
|
|
14
|
+
* not ask to stream is streamed and folded back into one final;
|
|
15
|
+
* - none of the parameters the preview refuses: `max_output_tokens`,
|
|
16
|
+
* `temperature`, `top_p`;
|
|
17
|
+
* - no SDK retries: a plan-usage limit (429) is reported, not hammered.
|
|
18
|
+
* The provider id stays `openai`, so reasoning replay and the stored events
|
|
19
|
+
* are the same as on a key.
|
|
20
|
+
*
|
|
21
|
+
* On a served surface (lib/chatgpt/state.ts markServedSurface) every call
|
|
22
|
+
* ends in a CHATGPT_SIGNIN_LOCAL_ONLY final before any token is read.
|
|
23
|
+
*/
|
|
24
|
+
import type { ModelRequest, ModelResponse } from '../models/contract.ts';
|
|
25
|
+
import type { ProviderEndpoint } from '../models/endpoints.ts';
|
|
26
|
+
import { GptAdapter } from '../models/gptAdapter.ts';
|
|
27
|
+
import type { OAuthOptions } from './oauth.ts';
|
|
28
|
+
export interface ChatGptSignInAdapterOptions extends OAuthOptions {
|
|
29
|
+
model: string;
|
|
30
|
+
/** The credential file; default signInFile(). */
|
|
31
|
+
file?: string;
|
|
32
|
+
}
|
|
33
|
+
export declare class ChatGptSignInAdapter extends GptAdapter {
|
|
34
|
+
#private;
|
|
35
|
+
protected readonly label: string;
|
|
36
|
+
constructor(options: ChatGptSignInAdapterOptions);
|
|
37
|
+
protected endpoint(): ProviderEndpoint;
|
|
38
|
+
protected baseURL(): string;
|
|
39
|
+
protected missingKeyMessage(): string;
|
|
40
|
+
/** The token source: read (and refreshed) on every request, never held in the adapter. */
|
|
41
|
+
protected clientAuth(): {
|
|
42
|
+
apiKey: () => Promise<string>;
|
|
43
|
+
baseURL: string;
|
|
44
|
+
};
|
|
45
|
+
protected clientOptions(): Record<string, unknown>;
|
|
46
|
+
/** The preview takes none of the sampling fields. */
|
|
47
|
+
protected acceptsSampling(): boolean;
|
|
48
|
+
requestBody(request: ModelRequest, model?: string, endpoint?: ProviderEndpoint): Record<string, unknown>;
|
|
49
|
+
generate(request: ModelRequest): AsyncGenerator<ModelResponse, void>;
|
|
50
|
+
}
|