gsd-pi 2.75.0-dev.a44b82572 → 2.75.0-dev.e41b70b10

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.
Files changed (111) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +2 -0
  2. package/dist/resources/extensions/gsd/auto-dashboard.js +22 -1
  3. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +8 -2
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +11 -11
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +3 -1
  6. package/dist/resources/extensions/gsd/auto-prompts.js +19 -9
  7. package/dist/resources/extensions/gsd/auto-worktree.js +16 -1
  8. package/dist/resources/extensions/gsd/doctor-git-checks.js +22 -2
  9. package/dist/resources/extensions/gsd/pre-execution-checks.js +12 -8
  10. package/dist/resources/extensions/gsd/prompts/add-tests.md +1 -0
  11. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  12. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  13. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +14 -0
  14. package/dist/resources/extensions/search-the-web/command-search-provider.js +4 -1
  15. package/dist/resources/extensions/search-the-web/native-search.js +13 -2
  16. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  17. package/dist/web/standalone/.next/BUILD_ID +1 -1
  18. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  19. package/dist/web/standalone/.next/build-manifest.json +2 -2
  20. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  21. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.html +1 -1
  38. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  45. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  47. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  48. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  49. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  50. package/package.json +1 -1
  51. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  52. package/packages/mcp-server/dist/workflow-tools.js +102 -65
  53. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  54. package/packages/mcp-server/src/workflow-tools.test.ts +255 -0
  55. package/packages/mcp-server/src/workflow-tools.ts +108 -65
  56. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  57. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  58. package/packages/pi-ai/dist/index.d.ts +1 -0
  59. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  60. package/packages/pi-ai/dist/index.js +1 -0
  61. package/packages/pi-ai/dist/index.js.map +1 -1
  62. package/packages/pi-ai/dist/providers/api-family.d.ts +27 -0
  63. package/packages/pi-ai/dist/providers/api-family.d.ts.map +1 -0
  64. package/packages/pi-ai/dist/providers/api-family.js +47 -0
  65. package/packages/pi-ai/dist/providers/api-family.js.map +1 -0
  66. package/packages/pi-ai/dist/providers/api-family.test.d.ts +2 -0
  67. package/packages/pi-ai/dist/providers/api-family.test.d.ts.map +1 -0
  68. package/packages/pi-ai/dist/providers/api-family.test.js +101 -0
  69. package/packages/pi-ai/dist/providers/api-family.test.js.map +1 -0
  70. package/packages/pi-ai/src/index.ts +1 -0
  71. package/packages/pi-ai/src/providers/api-family.test.ts +129 -0
  72. package/packages/pi-ai/src/providers/api-family.ts +57 -0
  73. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  74. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
  75. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  78. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  81. package/packages/pi-coding-agent/dist/core/retry-handler.js +4 -1
  82. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  83. package/packages/pi-coding-agent/src/core/extensions/runner.ts +4 -1
  84. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -2
  85. package/packages/pi-coding-agent/src/core/retry-handler.ts +4 -1
  86. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  87. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -10
  88. package/src/resources/extensions/gsd/auto/phases.ts +3 -0
  89. package/src/resources/extensions/gsd/auto-dashboard.ts +25 -1
  90. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +15 -2
  91. package/src/resources/extensions/gsd/auto-dispatch.ts +21 -7
  92. package/src/resources/extensions/gsd/auto-model-selection.ts +3 -1
  93. package/src/resources/extensions/gsd/auto-prompts.ts +33 -9
  94. package/src/resources/extensions/gsd/auto-worktree.ts +16 -1
  95. package/src/resources/extensions/gsd/doctor-git-checks.ts +23 -2
  96. package/src/resources/extensions/gsd/pre-execution-checks.ts +12 -8
  97. package/src/resources/extensions/gsd/prompts/add-tests.md +1 -0
  98. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  99. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -0
  100. package/src/resources/extensions/gsd/tests/auto-dashboard.test.ts +49 -0
  101. package/src/resources/extensions/gsd/tests/integration/doctor-git-symlink-cwd.test.ts +79 -0
  102. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +66 -0
  103. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +132 -8
  104. package/src/resources/extensions/gsd/tests/prompts-no-gitignored-test-refs.test.ts +56 -0
  105. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +54 -0
  106. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +97 -0
  107. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +14 -0
  108. package/src/resources/extensions/search-the-web/command-search-provider.ts +4 -1
  109. package/src/resources/extensions/search-the-web/native-search.ts +13 -3
  110. /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_buildManifest.js +0 -0
  111. /package/dist/web/standalone/.next/static/{iBwPQUj73sn8jxegTo320 → By_yegSJ-AA1OP0QjYbSl}/_ssgManifest.js +0 -0
@@ -4,6 +4,7 @@ export * from "./api-registry.js";
4
4
  export * from "./env-api-keys.js";
5
5
  export * from "./models/index.js";
6
6
  export { mapThinkingLevelToEffort, supportsAdaptiveThinking, } from "./providers/anthropic-shared.js";
7
+ export * from "./providers/api-family.js";
7
8
  export * from "./providers/provider-capabilities.js";
8
9
  export * from "./providers/register-builtins.js";
9
10
  export type { ProviderSwitchReport } from "./providers/transform-messages.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GACxB,MAAM,iCAAiC,CAAC;AACzC,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AACvH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,YAAY,EACX,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,sBAAsB,GACtB,MAAM,wBAAwB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GACxB,MAAM,iCAAiC,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AACjD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AACvH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,YAAY,EACX,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,sBAAsB,GACtB,MAAM,wBAAwB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC"}
@@ -3,6 +3,7 @@ export * from "./api-registry.js";
3
3
  export * from "./env-api-keys.js";
4
4
  export * from "./models/index.js";
5
5
  export { mapThinkingLevelToEffort, supportsAdaptiveThinking, } from "./providers/anthropic-shared.js";
6
+ export * from "./providers/api-family.js";
6
7
  export * from "./providers/provider-capabilities.js";
7
8
  export * from "./providers/register-builtins.js";
8
9
  export { createEmptyReport, hasTransformations, transformMessagesWithReport } from "./providers/transform-messages.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GACxB,MAAM,iCAAiC,CAAC;AACzC,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AAEjD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AACvH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AAStC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC","sourcesContent":["export type { Static, TSchema } from \"@sinclair/typebox\";\nexport { Type } from \"@sinclair/typebox\";\n\nexport * from \"./api-registry.js\";\nexport * from \"./env-api-keys.js\";\nexport * from \"./models/index.js\";\nexport {\n\tmapThinkingLevelToEffort,\n\tsupportsAdaptiveThinking,\n} from \"./providers/anthropic-shared.js\";\nexport * from \"./providers/provider-capabilities.js\";\nexport * from \"./providers/register-builtins.js\";\nexport type { ProviderSwitchReport } from \"./providers/transform-messages.js\";\nexport { createEmptyReport, hasTransformations, transformMessagesWithReport } from \"./providers/transform-messages.js\";\nexport * from \"./stream.js\";\nexport * from \"./types.js\";\nexport * from \"./utils/event-stream.js\";\nexport * from \"./utils/json-parse.js\";\nexport type {\n\tOAuthAuthInfo,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthPrompt,\n\tOAuthProviderId,\n\tOAuthProviderInterface,\n} from \"./utils/oauth/types.js\";\nexport * from \"./utils/overflow.js\";\nexport * from \"./utils/typebox-helpers.js\";\nexport * from \"./utils/repair-tool-json.js\";\nexport * from \"./utils/validation.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,OAAO,EACN,wBAAwB,EACxB,wBAAwB,GACxB,MAAM,iCAAiC,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,sCAAsC,CAAC;AACrD,cAAc,kCAAkC,CAAC;AAEjD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,2BAA2B,EAAE,MAAM,mCAAmC,CAAC;AACvH,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AAStC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,uBAAuB,CAAC","sourcesContent":["export type { Static, TSchema } from \"@sinclair/typebox\";\nexport { Type } from \"@sinclair/typebox\";\n\nexport * from \"./api-registry.js\";\nexport * from \"./env-api-keys.js\";\nexport * from \"./models/index.js\";\nexport {\n\tmapThinkingLevelToEffort,\n\tsupportsAdaptiveThinking,\n} from \"./providers/anthropic-shared.js\";\nexport * from \"./providers/api-family.js\";\nexport * from \"./providers/provider-capabilities.js\";\nexport * from \"./providers/register-builtins.js\";\nexport type { ProviderSwitchReport } from \"./providers/transform-messages.js\";\nexport { createEmptyReport, hasTransformations, transformMessagesWithReport } from \"./providers/transform-messages.js\";\nexport * from \"./stream.js\";\nexport * from \"./types.js\";\nexport * from \"./utils/event-stream.js\";\nexport * from \"./utils/json-parse.js\";\nexport type {\n\tOAuthAuthInfo,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthPrompt,\n\tOAuthProviderId,\n\tOAuthProviderInterface,\n} from \"./utils/oauth/types.js\";\nexport * from \"./utils/overflow.js\";\nexport * from \"./utils/typebox-helpers.js\";\nexport * from \"./utils/repair-tool-json.js\";\nexport * from \"./utils/validation.js\";\n"]}
@@ -0,0 +1,27 @@
1
+ /** Minimal shape — any object with an optional `api` string works. */
2
+ type HasApi = {
3
+ api?: string;
4
+ } | null | undefined;
5
+ /**
6
+ * True for any transport that speaks the Anthropic Messages wire protocol:
7
+ * direct Anthropic, Claude Code OAuth, Anthropic-on-Vertex, Vercel AI Gateway
8
+ * Anthropic routes, etc.
9
+ *
10
+ * Excludes Bedrock-hosted Claude — Bedrock Converse uses a different tool
11
+ * schema and is matched by `isBedrockApi` instead.
12
+ */
13
+ export declare function isAnthropicApi(model: HasApi): boolean;
14
+ /**
15
+ * True for any transport that speaks an OpenAI-shaped wire protocol:
16
+ * Chat Completions, Responses, Azure-hosted Responses, Codex-hosted Responses.
17
+ */
18
+ export declare function isOpenAIApi(model: HasApi): boolean;
19
+ /**
20
+ * True for any transport that speaks a Google Gemini wire protocol:
21
+ * Generative AI REST, Gemini CLI, Vertex AI.
22
+ */
23
+ export declare function isGeminiApi(model: HasApi): boolean;
24
+ /** True for AWS Bedrock Converse transports. */
25
+ export declare function isBedrockApi(model: HasApi): boolean;
26
+ export {};
27
+ //# sourceMappingURL=api-family.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-family.d.ts","sourceRoot":"","sources":["../../src/providers/api-family.ts"],"names":[],"mappings":"AAUA,sEAAsE;AACtE,KAAK,MAAM,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAElD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGrD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAQlD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,gDAAgD;AAChD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEnD"}
@@ -0,0 +1,47 @@
1
+ // gsd-2 / pi-ai: API-shape family predicates
2
+ //
3
+ // Rule (see docs/dev/ADR-012-provider-id-vs-api-shape.md):
4
+ // Gate API-shape-dependent behavior (tool schemas, request payload shape,
5
+ // streaming format) on `model.api`, never on `model.provider`.
6
+ // `provider` is a credential/transport identifier that varies by vendor
7
+ // routing (e.g. `anthropic`, `claude-code`, `anthropic-vertex`,
8
+ // `amazon-bedrock`, `vercel-ai-gateway`) even when the wire protocol is
9
+ // identical.
10
+ /**
11
+ * True for any transport that speaks the Anthropic Messages wire protocol:
12
+ * direct Anthropic, Claude Code OAuth, Anthropic-on-Vertex, Vercel AI Gateway
13
+ * Anthropic routes, etc.
14
+ *
15
+ * Excludes Bedrock-hosted Claude — Bedrock Converse uses a different tool
16
+ * schema and is matched by `isBedrockApi` instead.
17
+ */
18
+ export function isAnthropicApi(model) {
19
+ const api = model?.api;
20
+ return api === "anthropic-messages" || api === "anthropic-vertex";
21
+ }
22
+ /**
23
+ * True for any transport that speaks an OpenAI-shaped wire protocol:
24
+ * Chat Completions, Responses, Azure-hosted Responses, Codex-hosted Responses.
25
+ */
26
+ export function isOpenAIApi(model) {
27
+ const api = model?.api;
28
+ return (api === "openai-completions" ||
29
+ api === "openai-responses" ||
30
+ api === "azure-openai-responses" ||
31
+ api === "openai-codex-responses");
32
+ }
33
+ /**
34
+ * True for any transport that speaks a Google Gemini wire protocol:
35
+ * Generative AI REST, Gemini CLI, Vertex AI.
36
+ */
37
+ export function isGeminiApi(model) {
38
+ const api = model?.api;
39
+ return (api === "google-generative-ai" ||
40
+ api === "google-gemini-cli" ||
41
+ api === "google-vertex");
42
+ }
43
+ /** True for AWS Bedrock Converse transports. */
44
+ export function isBedrockApi(model) {
45
+ return model?.api === "bedrock-converse-stream";
46
+ }
47
+ //# sourceMappingURL=api-family.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-family.js","sourceRoot":"","sources":["../../src/providers/api-family.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,EAAE;AACF,2DAA2D;AAC3D,4EAA4E;AAC5E,iEAAiE;AACjE,0EAA0E;AAC1E,kEAAkE;AAClE,0EAA0E;AAC1E,eAAe;AAKf;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,CAAC;IACvB,OAAO,GAAG,KAAK,oBAAoB,IAAI,GAAG,KAAK,kBAAkB,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,CAAC;IACvB,OAAO,CACL,GAAG,KAAK,oBAAoB;QAC5B,GAAG,KAAK,kBAAkB;QAC1B,GAAG,KAAK,wBAAwB;QAChC,GAAG,KAAK,wBAAwB,CACjC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,CAAC;IACvB,OAAO,CACL,GAAG,KAAK,sBAAsB;QAC9B,GAAG,KAAK,mBAAmB;QAC3B,GAAG,KAAK,eAAe,CACxB,CAAC;AACJ,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,KAAK,EAAE,GAAG,KAAK,yBAAyB,CAAC;AAClD,CAAC","sourcesContent":["// gsd-2 / pi-ai: API-shape family predicates\n//\n// Rule (see docs/dev/ADR-012-provider-id-vs-api-shape.md):\n// Gate API-shape-dependent behavior (tool schemas, request payload shape,\n// streaming format) on `model.api`, never on `model.provider`.\n// `provider` is a credential/transport identifier that varies by vendor\n// routing (e.g. `anthropic`, `claude-code`, `anthropic-vertex`,\n// `amazon-bedrock`, `vercel-ai-gateway`) even when the wire protocol is\n// identical.\n\n/** Minimal shape — any object with an optional `api` string works. */\ntype HasApi = { api?: string } | null | undefined;\n\n/**\n * True for any transport that speaks the Anthropic Messages wire protocol:\n * direct Anthropic, Claude Code OAuth, Anthropic-on-Vertex, Vercel AI Gateway\n * Anthropic routes, etc.\n *\n * Excludes Bedrock-hosted Claude — Bedrock Converse uses a different tool\n * schema and is matched by `isBedrockApi` instead.\n */\nexport function isAnthropicApi(model: HasApi): boolean {\n const api = model?.api;\n return api === \"anthropic-messages\" || api === \"anthropic-vertex\";\n}\n\n/**\n * True for any transport that speaks an OpenAI-shaped wire protocol:\n * Chat Completions, Responses, Azure-hosted Responses, Codex-hosted Responses.\n */\nexport function isOpenAIApi(model: HasApi): boolean {\n const api = model?.api;\n return (\n api === \"openai-completions\" ||\n api === \"openai-responses\" ||\n api === \"azure-openai-responses\" ||\n api === \"openai-codex-responses\"\n );\n}\n\n/**\n * True for any transport that speaks a Google Gemini wire protocol:\n * Generative AI REST, Gemini CLI, Vertex AI.\n */\nexport function isGeminiApi(model: HasApi): boolean {\n const api = model?.api;\n return (\n api === \"google-generative-ai\" ||\n api === \"google-gemini-cli\" ||\n api === \"google-vertex\"\n );\n}\n\n/** True for AWS Bedrock Converse transports. */\nexport function isBedrockApi(model: HasApi): boolean {\n return model?.api === \"bedrock-converse-stream\";\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=api-family.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-family.test.d.ts","sourceRoot":"","sources":["../../src/providers/api-family.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ // gsd-2 / pi-ai: api-family predicate tests
2
+ import { describe, test } from "node:test";
3
+ import assert from "node:assert/strict";
4
+ import { isAnthropicApi, isBedrockApi, isGeminiApi, isOpenAIApi, } from "./api-family.js";
5
+ // Every api value registered via registerApiProvider() in register-builtins.ts.
6
+ // Keep in sync with that file — the expectations below assert every api is
7
+ // classified by exactly one family (except mistral, which is its own family
8
+ // and belongs to none of the helpers).
9
+ const ALL_REGISTERED_APIS = [
10
+ "anthropic-messages",
11
+ "anthropic-vertex",
12
+ "openai-completions",
13
+ "openai-responses",
14
+ "azure-openai-responses",
15
+ "openai-codex-responses",
16
+ "google-generative-ai",
17
+ "google-gemini-cli",
18
+ "google-vertex",
19
+ "bedrock-converse-stream",
20
+ "mistral-conversations",
21
+ ];
22
+ describe("isAnthropicApi", () => {
23
+ test("matches anthropic-messages and anthropic-vertex", () => {
24
+ assert.equal(isAnthropicApi({ api: "anthropic-messages" }), true);
25
+ assert.equal(isAnthropicApi({ api: "anthropic-vertex" }), true);
26
+ });
27
+ test("excludes bedrock-converse-stream (different tool schema)", () => {
28
+ assert.equal(isAnthropicApi({ api: "bedrock-converse-stream" }), false);
29
+ });
30
+ test("excludes every non-Anthropic registered api", () => {
31
+ const nonAnthropic = ALL_REGISTERED_APIS.filter((a) => a !== "anthropic-messages" && a !== "anthropic-vertex");
32
+ for (const api of nonAnthropic) {
33
+ assert.equal(isAnthropicApi({ api }), false, `api=${api}`);
34
+ }
35
+ });
36
+ test("tolerates null/undefined/missing api", () => {
37
+ assert.equal(isAnthropicApi(null), false);
38
+ assert.equal(isAnthropicApi(undefined), false);
39
+ assert.equal(isAnthropicApi({}), false);
40
+ assert.equal(isAnthropicApi({ api: "" }), false);
41
+ });
42
+ });
43
+ describe("isOpenAIApi", () => {
44
+ test("matches all OpenAI-shaped apis", () => {
45
+ for (const api of [
46
+ "openai-completions",
47
+ "openai-responses",
48
+ "azure-openai-responses",
49
+ "openai-codex-responses",
50
+ ]) {
51
+ assert.equal(isOpenAIApi({ api }), true, `api=${api}`);
52
+ }
53
+ });
54
+ test("excludes every non-OpenAI registered api", () => {
55
+ const nonOpenAI = ALL_REGISTERED_APIS.filter((a) => a !== "openai-completions" &&
56
+ a !== "openai-responses" &&
57
+ a !== "azure-openai-responses" &&
58
+ a !== "openai-codex-responses");
59
+ for (const api of nonOpenAI) {
60
+ assert.equal(isOpenAIApi({ api }), false, `api=${api}`);
61
+ }
62
+ });
63
+ });
64
+ describe("isGeminiApi", () => {
65
+ test("matches all Gemini-shaped apis", () => {
66
+ for (const api of ["google-generative-ai", "google-gemini-cli", "google-vertex"]) {
67
+ assert.equal(isGeminiApi({ api }), true, `api=${api}`);
68
+ }
69
+ });
70
+ test("excludes every non-Gemini registered api", () => {
71
+ const nonGemini = ALL_REGISTERED_APIS.filter((a) => a !== "google-generative-ai" &&
72
+ a !== "google-gemini-cli" &&
73
+ a !== "google-vertex");
74
+ for (const api of nonGemini) {
75
+ assert.equal(isGeminiApi({ api }), false, `api=${api}`);
76
+ }
77
+ });
78
+ });
79
+ describe("isBedrockApi", () => {
80
+ test("matches only bedrock-converse-stream", () => {
81
+ assert.equal(isBedrockApi({ api: "bedrock-converse-stream" }), true);
82
+ for (const api of ALL_REGISTERED_APIS.filter((a) => a !== "bedrock-converse-stream")) {
83
+ assert.equal(isBedrockApi({ api }), false, `api=${api}`);
84
+ }
85
+ });
86
+ });
87
+ describe("api-family exclusivity", () => {
88
+ test("every registered api belongs to exactly one family (or mistral = none)", () => {
89
+ for (const api of ALL_REGISTERED_APIS) {
90
+ const matches = [
91
+ isAnthropicApi({ api }),
92
+ isOpenAIApi({ api }),
93
+ isGeminiApi({ api }),
94
+ isBedrockApi({ api }),
95
+ ].filter(Boolean).length;
96
+ const expected = api === "mistral-conversations" ? 0 : 1;
97
+ assert.equal(matches, expected, `api=${api} matched ${matches} families (expected ${expected})`);
98
+ }
99
+ });
100
+ });
101
+ //# sourceMappingURL=api-family.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-family.test.js","sourceRoot":"","sources":["../../src/providers/api-family.test.ts"],"names":[],"mappings":"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","sourcesContent":["// gsd-2 / pi-ai: api-family predicate tests\nimport { describe, test } from \"node:test\";\nimport assert from \"node:assert/strict\";\n\nimport {\n isAnthropicApi,\n isBedrockApi,\n isGeminiApi,\n isOpenAIApi,\n} from \"./api-family.js\";\n\n// Every api value registered via registerApiProvider() in register-builtins.ts.\n// Keep in sync with that file — the expectations below assert every api is\n// classified by exactly one family (except mistral, which is its own family\n// and belongs to none of the helpers).\nconst ALL_REGISTERED_APIS = [\n \"anthropic-messages\",\n \"anthropic-vertex\",\n \"openai-completions\",\n \"openai-responses\",\n \"azure-openai-responses\",\n \"openai-codex-responses\",\n \"google-generative-ai\",\n \"google-gemini-cli\",\n \"google-vertex\",\n \"bedrock-converse-stream\",\n \"mistral-conversations\",\n] as const;\n\ndescribe(\"isAnthropicApi\", () => {\n test(\"matches anthropic-messages and anthropic-vertex\", () => {\n assert.equal(isAnthropicApi({ api: \"anthropic-messages\" }), true);\n assert.equal(isAnthropicApi({ api: \"anthropic-vertex\" }), true);\n });\n\n test(\"excludes bedrock-converse-stream (different tool schema)\", () => {\n assert.equal(isAnthropicApi({ api: \"bedrock-converse-stream\" }), false);\n });\n\n test(\"excludes every non-Anthropic registered api\", () => {\n const nonAnthropic = ALL_REGISTERED_APIS.filter(\n (a) => a !== \"anthropic-messages\" && a !== \"anthropic-vertex\",\n );\n for (const api of nonAnthropic) {\n assert.equal(isAnthropicApi({ api }), false, `api=${api}`);\n }\n });\n\n test(\"tolerates null/undefined/missing api\", () => {\n assert.equal(isAnthropicApi(null), false);\n assert.equal(isAnthropicApi(undefined), false);\n assert.equal(isAnthropicApi({}), false);\n assert.equal(isAnthropicApi({ api: \"\" }), false);\n });\n});\n\ndescribe(\"isOpenAIApi\", () => {\n test(\"matches all OpenAI-shaped apis\", () => {\n for (const api of [\n \"openai-completions\",\n \"openai-responses\",\n \"azure-openai-responses\",\n \"openai-codex-responses\",\n ]) {\n assert.equal(isOpenAIApi({ api }), true, `api=${api}`);\n }\n });\n\n test(\"excludes every non-OpenAI registered api\", () => {\n const nonOpenAI = ALL_REGISTERED_APIS.filter(\n (a) =>\n a !== \"openai-completions\" &&\n a !== \"openai-responses\" &&\n a !== \"azure-openai-responses\" &&\n a !== \"openai-codex-responses\",\n );\n for (const api of nonOpenAI) {\n assert.equal(isOpenAIApi({ api }), false, `api=${api}`);\n }\n });\n});\n\ndescribe(\"isGeminiApi\", () => {\n test(\"matches all Gemini-shaped apis\", () => {\n for (const api of [\"google-generative-ai\", \"google-gemini-cli\", \"google-vertex\"]) {\n assert.equal(isGeminiApi({ api }), true, `api=${api}`);\n }\n });\n\n test(\"excludes every non-Gemini registered api\", () => {\n const nonGemini = ALL_REGISTERED_APIS.filter(\n (a) =>\n a !== \"google-generative-ai\" &&\n a !== \"google-gemini-cli\" &&\n a !== \"google-vertex\",\n );\n for (const api of nonGemini) {\n assert.equal(isGeminiApi({ api }), false, `api=${api}`);\n }\n });\n});\n\ndescribe(\"isBedrockApi\", () => {\n test(\"matches only bedrock-converse-stream\", () => {\n assert.equal(isBedrockApi({ api: \"bedrock-converse-stream\" }), true);\n for (const api of ALL_REGISTERED_APIS.filter((a) => a !== \"bedrock-converse-stream\")) {\n assert.equal(isBedrockApi({ api }), false, `api=${api}`);\n }\n });\n});\n\ndescribe(\"api-family exclusivity\", () => {\n test(\"every registered api belongs to exactly one family (or mistral = none)\", () => {\n for (const api of ALL_REGISTERED_APIS) {\n const matches = [\n isAnthropicApi({ api }),\n isOpenAIApi({ api }),\n isGeminiApi({ api }),\n isBedrockApi({ api }),\n ].filter(Boolean).length;\n const expected = api === \"mistral-conversations\" ? 0 : 1;\n assert.equal(\n matches,\n expected,\n `api=${api} matched ${matches} families (expected ${expected})`,\n );\n }\n });\n});\n"]}
@@ -8,6 +8,7 @@ export {
8
8
  mapThinkingLevelToEffort,
9
9
  supportsAdaptiveThinking,
10
10
  } from "./providers/anthropic-shared.js";
11
+ export * from "./providers/api-family.js";
11
12
  export * from "./providers/provider-capabilities.js";
12
13
  export * from "./providers/register-builtins.js";
13
14
  export type { ProviderSwitchReport } from "./providers/transform-messages.js";
@@ -0,0 +1,129 @@
1
+ // gsd-2 / pi-ai: api-family predicate tests
2
+ import { describe, test } from "node:test";
3
+ import assert from "node:assert/strict";
4
+
5
+ import {
6
+ isAnthropicApi,
7
+ isBedrockApi,
8
+ isGeminiApi,
9
+ isOpenAIApi,
10
+ } from "./api-family.js";
11
+
12
+ // Every api value registered via registerApiProvider() in register-builtins.ts.
13
+ // Keep in sync with that file — the expectations below assert every api is
14
+ // classified by exactly one family (except mistral, which is its own family
15
+ // and belongs to none of the helpers).
16
+ const ALL_REGISTERED_APIS = [
17
+ "anthropic-messages",
18
+ "anthropic-vertex",
19
+ "openai-completions",
20
+ "openai-responses",
21
+ "azure-openai-responses",
22
+ "openai-codex-responses",
23
+ "google-generative-ai",
24
+ "google-gemini-cli",
25
+ "google-vertex",
26
+ "bedrock-converse-stream",
27
+ "mistral-conversations",
28
+ ] as const;
29
+
30
+ describe("isAnthropicApi", () => {
31
+ test("matches anthropic-messages and anthropic-vertex", () => {
32
+ assert.equal(isAnthropicApi({ api: "anthropic-messages" }), true);
33
+ assert.equal(isAnthropicApi({ api: "anthropic-vertex" }), true);
34
+ });
35
+
36
+ test("excludes bedrock-converse-stream (different tool schema)", () => {
37
+ assert.equal(isAnthropicApi({ api: "bedrock-converse-stream" }), false);
38
+ });
39
+
40
+ test("excludes every non-Anthropic registered api", () => {
41
+ const nonAnthropic = ALL_REGISTERED_APIS.filter(
42
+ (a) => a !== "anthropic-messages" && a !== "anthropic-vertex",
43
+ );
44
+ for (const api of nonAnthropic) {
45
+ assert.equal(isAnthropicApi({ api }), false, `api=${api}`);
46
+ }
47
+ });
48
+
49
+ test("tolerates null/undefined/missing api", () => {
50
+ assert.equal(isAnthropicApi(null), false);
51
+ assert.equal(isAnthropicApi(undefined), false);
52
+ assert.equal(isAnthropicApi({}), false);
53
+ assert.equal(isAnthropicApi({ api: "" }), false);
54
+ });
55
+ });
56
+
57
+ describe("isOpenAIApi", () => {
58
+ test("matches all OpenAI-shaped apis", () => {
59
+ for (const api of [
60
+ "openai-completions",
61
+ "openai-responses",
62
+ "azure-openai-responses",
63
+ "openai-codex-responses",
64
+ ]) {
65
+ assert.equal(isOpenAIApi({ api }), true, `api=${api}`);
66
+ }
67
+ });
68
+
69
+ test("excludes every non-OpenAI registered api", () => {
70
+ const nonOpenAI = ALL_REGISTERED_APIS.filter(
71
+ (a) =>
72
+ a !== "openai-completions" &&
73
+ a !== "openai-responses" &&
74
+ a !== "azure-openai-responses" &&
75
+ a !== "openai-codex-responses",
76
+ );
77
+ for (const api of nonOpenAI) {
78
+ assert.equal(isOpenAIApi({ api }), false, `api=${api}`);
79
+ }
80
+ });
81
+ });
82
+
83
+ describe("isGeminiApi", () => {
84
+ test("matches all Gemini-shaped apis", () => {
85
+ for (const api of ["google-generative-ai", "google-gemini-cli", "google-vertex"]) {
86
+ assert.equal(isGeminiApi({ api }), true, `api=${api}`);
87
+ }
88
+ });
89
+
90
+ test("excludes every non-Gemini registered api", () => {
91
+ const nonGemini = ALL_REGISTERED_APIS.filter(
92
+ (a) =>
93
+ a !== "google-generative-ai" &&
94
+ a !== "google-gemini-cli" &&
95
+ a !== "google-vertex",
96
+ );
97
+ for (const api of nonGemini) {
98
+ assert.equal(isGeminiApi({ api }), false, `api=${api}`);
99
+ }
100
+ });
101
+ });
102
+
103
+ describe("isBedrockApi", () => {
104
+ test("matches only bedrock-converse-stream", () => {
105
+ assert.equal(isBedrockApi({ api: "bedrock-converse-stream" }), true);
106
+ for (const api of ALL_REGISTERED_APIS.filter((a) => a !== "bedrock-converse-stream")) {
107
+ assert.equal(isBedrockApi({ api }), false, `api=${api}`);
108
+ }
109
+ });
110
+ });
111
+
112
+ describe("api-family exclusivity", () => {
113
+ test("every registered api belongs to exactly one family (or mistral = none)", () => {
114
+ for (const api of ALL_REGISTERED_APIS) {
115
+ const matches = [
116
+ isAnthropicApi({ api }),
117
+ isOpenAIApi({ api }),
118
+ isGeminiApi({ api }),
119
+ isBedrockApi({ api }),
120
+ ].filter(Boolean).length;
121
+ const expected = api === "mistral-conversations" ? 0 : 1;
122
+ assert.equal(
123
+ matches,
124
+ expected,
125
+ `api=${api} matched ${matches} families (expected ${expected})`,
126
+ );
127
+ }
128
+ });
129
+ });
@@ -0,0 +1,57 @@
1
+ // gsd-2 / pi-ai: API-shape family predicates
2
+ //
3
+ // Rule (see docs/dev/ADR-012-provider-id-vs-api-shape.md):
4
+ // Gate API-shape-dependent behavior (tool schemas, request payload shape,
5
+ // streaming format) on `model.api`, never on `model.provider`.
6
+ // `provider` is a credential/transport identifier that varies by vendor
7
+ // routing (e.g. `anthropic`, `claude-code`, `anthropic-vertex`,
8
+ // `amazon-bedrock`, `vercel-ai-gateway`) even when the wire protocol is
9
+ // identical.
10
+
11
+ /** Minimal shape — any object with an optional `api` string works. */
12
+ type HasApi = { api?: string } | null | undefined;
13
+
14
+ /**
15
+ * True for any transport that speaks the Anthropic Messages wire protocol:
16
+ * direct Anthropic, Claude Code OAuth, Anthropic-on-Vertex, Vercel AI Gateway
17
+ * Anthropic routes, etc.
18
+ *
19
+ * Excludes Bedrock-hosted Claude — Bedrock Converse uses a different tool
20
+ * schema and is matched by `isBedrockApi` instead.
21
+ */
22
+ export function isAnthropicApi(model: HasApi): boolean {
23
+ const api = model?.api;
24
+ return api === "anthropic-messages" || api === "anthropic-vertex";
25
+ }
26
+
27
+ /**
28
+ * True for any transport that speaks an OpenAI-shaped wire protocol:
29
+ * Chat Completions, Responses, Azure-hosted Responses, Codex-hosted Responses.
30
+ */
31
+ export function isOpenAIApi(model: HasApi): boolean {
32
+ const api = model?.api;
33
+ return (
34
+ api === "openai-completions" ||
35
+ api === "openai-responses" ||
36
+ api === "azure-openai-responses" ||
37
+ api === "openai-codex-responses"
38
+ );
39
+ }
40
+
41
+ /**
42
+ * True for any transport that speaks a Google Gemini wire protocol:
43
+ * Generative AI REST, Gemini CLI, Vertex AI.
44
+ */
45
+ export function isGeminiApi(model: HasApi): boolean {
46
+ const api = model?.api;
47
+ return (
48
+ api === "google-generative-ai" ||
49
+ api === "google-gemini-cli" ||
50
+ api === "google-vertex"
51
+ );
52
+ }
53
+
54
+ /** True for AWS Bedrock Converse transports. */
55
+ export function isBedrockApi(model: HasApi): boolean {
56
+ return model?.api === "bedrock-converse-stream";
57
+ }