Skip to content

feat(ai): add schema-constrained structured completion - #19

Draft
jordanrinke wants to merge 23 commits into
dutchie-0.80.6from
feat/structured-output-api
Draft

feat(ai): add schema-constrained structured completion#19
jordanrinke wants to merge 23 commits into
dutchie-0.80.6from
feat/structured-output-api

Conversation

@jordanrinke

Copy link
Copy Markdown

Summary

Adds the additive completeStructured() API in @earendil-works/pi-ai/compat.
It creates one internal schema-constrained output tool, forces that tool on builtin
providers where supported, validates the returned arguments against the original
schema, and never accepts assistant text as JSON. Existing complete*/stream*
APIs and normal agent tool calling remain unchanged.

Gemini follow-up required

A live typed probe of google/gemini-3.1-pro-preview authenticated successfully,
but returned no function call even though the request contained one output tool and
Gemini FunctionCallingConfig.mode = ANY. completeStructured() correctly failed
closed with:

Structured completion expected exactly one submit_structured_output call, received 0

The same schema-constrained probe passed for Bedrock Claude Fable 5, Azure GPT-5.6
Terra, and Bedrock Sonnet 5. Do not use Gemini 3.1 Pro as a structured-output
reviewer until this provider behavior is fixed and verified by a real probe.

Likely follow-up: determine whether Gemini 3.1 requires an explicit
allowedFunctionNames: ["submit_structured_output"], a different function schema
field, or native response-schema handling. That work is intentionally not included
in this PR; no speculative Gemini provider change is committed.

Verification

  • focused @earendil-works/pi-ai tests: 24 passed
  • npm run check passed
  • live typed probes: Fable, Azure Terra, and Sonnet passed; Gemini failed closed as described above

mitsuhiko and others added 23 commits July 10, 2026 01:16
this supports BUN_CONFIG_HTTP_IDLE_TIMEOUT
related to earendil-works#6476
This adds cache-friendly dynamic tool loading anchored to tool results. Purely additive active-tool changes are recorded with `addedToolNames`, allowing supported Anthropic and OpenAI Responses models to load tool definitions at the point they become available instead of placing them in the cached prompt prefix.

It retains safe fallback behavior for unsupported models and non-additive changes but it will wipe caches.
Do not treat Pi’s internal ambient-auth marker as a Bedrock bearer token. This preserves SigV4 signing for AWS profiles, IAM credentials, and roles while retaining bearer authentication for real Bedrock API keys.\n\nFixes earendil-works#6531
…entials (earendil-works#6292)

Cloudflare Workers AI / AI Gateway resolved provider config from the
credential only, never consulting ambient env for a field the credential
omitted. The coding-agent /login flow stores just the API key, so
CLOUDFLARE_ACCOUNT_ID lives only in the environment; the key-only credential
short-circuited the env lookup, the account id stayed unresolved, and requests
hit the literal {CLOUDFLARE_ACCOUNT_ID} base URL -> 404.

resolveValue now merges per field: prefer the credential value, fall back to
ctx.env(name).

closes earendil-works#6021

Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
…n per tool (#21)

Tool.strict is a new opt-in boolean. OpenAI Chat Completions, shared
OpenAI/Azure Responses (including deferred tools), and Anthropic Messages
now strictify a tool's schema only when tool.strict === true. Ordinary
tools keep their original schema and reprompt-loop validation.

PI_STRICT_TOOLS=0 remains a global emergency kill switch that forces every
tool non-strict, including opted-in ones. Provider/model compat
(supportsStrictMode) remains a capability gate. Codex and Mistral are
unchanged (they already pass strict: null / false explicitly).

completeStructured()'s internal fallback output tool is marked
strict: true so existing structured-output behavior via context.tools is
preserved.

Propagate strict through coding-agent's ToolDefinition and both
tool-definition-wrapper conversions (wrapToolDefinition,
createToolDefinitionFromAgentTool) so extension-registered tools do not
silently lose the flag.

Tests:
- packages/ai/test/per-tool-strict.test.ts: ordinary vs opted-in vs mixed
  batches for OpenAI Chat, OpenAI Responses, Azure Responses, and
  Anthropic, plus PI_STRICT_TOOLS=0 kill-switch coverage.
- packages/ai/test/deferred-tools.test.ts: updated ordinary-tool
  expectations to non-strict; added opted-in immediate/deferred coverage
  for Anthropic and OpenAI Responses tool search.
- packages/coding-agent/test/tool-definition-wrapper.test.ts: strict flag
  propagation in both wrapper directions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants