codai-sdk 0.69.0__tar.gz → 0.71.0__tar.gz

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 (99) hide show
  1. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/CHANGELOG.md +12 -0
  2. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/PKG-INFO +1 -1
  3. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/pyproject.toml +1 -1
  4. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/__init__.py +5 -1
  5. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/_operations.py +1 -0
  6. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/_types.py +59 -8
  7. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/__init__.py +3 -1
  8. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/productions.py +17 -6
  9. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/vault.py +24 -2
  10. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_resources.py +1 -1
  11. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_vault.py +15 -3
  12. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  13. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  14. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/.github/dependabot.yml +0 -0
  15. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/.github/pull_request_template.md +0 -0
  16. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/.github/workflows/publish.yml +0 -0
  17. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/.gitignore +0 -0
  18. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/CODE_OF_CONDUCT.md +0 -0
  19. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/CONTRIBUTING.md +0 -0
  20. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/LICENSE +0 -0
  21. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/README.md +0 -0
  22. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/SECURITY.md +0 -0
  23. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/examples/agent.py +0 -0
  24. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/examples/chat.py +0 -0
  25. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/examples/feedback.py +0 -0
  26. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/examples/streaming.py +0 -0
  27. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/scripts/gen-types.py +0 -0
  28. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/scripts/smoke_local.py +0 -0
  29. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/_http.py +0 -0
  30. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/_resolve_types.py +0 -0
  31. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/_base.py +0 -0
  32. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/abuse_reports.py +0 -0
  33. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/agents.py +0 -0
  34. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/batches.py +0 -0
  35. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/billing.py +0 -0
  36. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/brand_kits.py +0 -0
  37. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/cards.py +0 -0
  38. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/chat.py +0 -0
  39. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/core.py +0 -0
  40. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/creatives.py +0 -0
  41. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/environments.py +0 -0
  42. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/exports.py +0 -0
  43. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/lists.py +0 -0
  44. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/lyrics.py +0 -0
  45. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/media_ops.py +0 -0
  46. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/memory.py +0 -0
  47. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/messages.py +0 -0
  48. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/music_room.py +0 -0
  49. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/platform.py +0 -0
  50. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/prompts.py +0 -0
  51. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/provenance.py +0 -0
  52. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/referrals.py +0 -0
  53. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/research.py +0 -0
  54. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/resolve.py +0 -0
  55. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/responses.py +0 -0
  56. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/sandboxes.py +0 -0
  57. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/schedules.py +0 -0
  58. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/sessions.py +0 -0
  59. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/shares.py +0 -0
  60. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/templates.py +0 -0
  61. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/transcripts.py +0 -0
  62. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/triggers.py +0 -0
  63. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/usage_keys.py +0 -0
  64. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/voice_revocations.py +0 -0
  65. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/src/codai/resources/voices.py +0 -0
  66. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/conftest.py +0 -0
  67. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_abuse_reports.py +0 -0
  68. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_batches.py +0 -0
  69. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_billing_referrals.py +0 -0
  70. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_brand_kits.py +0 -0
  71. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_cards.py +0 -0
  72. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_creatives.py +0 -0
  73. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_creatives_cx07.py +0 -0
  74. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_creatives_documents.py +0 -0
  75. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_creatives_e11.py +0 -0
  76. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_creatives_e11b.py +0 -0
  77. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_environments_exec.py +0 -0
  78. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_exports.py +0 -0
  79. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_http.py +0 -0
  80. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_lists.py +0 -0
  81. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_lyrics.py +0 -0
  82. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_media_ops.py +0 -0
  83. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_memory.py +0 -0
  84. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_music_room.py +0 -0
  85. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_os_gate.py +0 -0
  86. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_parity.py +0 -0
  87. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_productions.py +0 -0
  88. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_prompts.py +0 -0
  89. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_provenance.py +0 -0
  90. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_research.py +0 -0
  91. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_resolve.py +0 -0
  92. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_sandboxes.py +0 -0
  93. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_settings.py +0 -0
  94. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_shares_e12a.py +0 -0
  95. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_templates.py +0 -0
  96. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_transcripts.py +0 -0
  97. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_voice_revocations.py +0 -0
  98. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/tests/test_voices.py +0 -0
  99. {codai_sdk-0.69.0 → codai_sdk-0.71.0}/uv.lock +0 -0
@@ -6,6 +6,18 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.71.0] - 2026-10-24
10
+
11
+ - `productions.create(kind=..., duration_s=...)`: 8 production kinds (video, image, deck, doc, sheet, audio,
12
+ carousel, print) and a 15-60 s target length for video; non-video responses carry `kind`, `output_asset_id` and
13
+ `outputs`.
14
+
15
+ ## [0.70.0] - 2026-10-24
16
+
17
+ - `vault.resolve_challenge()` (`POST /v1/vault/resolve/challenge`) and a required `proof` on `vault.resolve()`: local
18
+ brokers prove an enrolled device key (`codai-vault/resolve/v1|<challenge>|<device_id>|<digest>`); helpers
19
+ `resolve_digest` / `resolve_message`. Device keys gain kind `cli_ed25519`, surface `cli`, attestation `software`.
20
+
9
21
  ## [0.69.0] - 2026-10-23
10
22
 
11
23
  - `MusicRoom.listening`: what the Music Room critic heard (listening features, Audiobox Aesthetics, Gemini listener
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: codai-sdk
3
- Version: 0.69.0
3
+ Version: 0.71.0
4
4
  Summary: Official Python SDK for the codai AI gateway — chat, messages, responses, streaming, agents, sessions, hosts, tasks, account.
5
5
  Project-URL: Homepage, https://codai.ro
6
6
  Project-URL: Repository, https://github.com/codai-ro/codai-sdk-python
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codai-sdk"
7
- version = "0.69.0"
7
+ version = "0.71.0"
8
8
  description = "Official Python SDK for the codai AI gateway — chat, messages, responses, streaming, agents, sessions, hosts, tasks, account."
9
9
  readme = "README.md"
10
10
 
@@ -65,6 +65,8 @@ from .resources import (
65
65
  VoiceRevocations,
66
66
  Voices,
67
67
  Vault,
68
+ vault_resolve_digest,
69
+ vault_resolve_message,
68
70
  vault_unlock_message,
69
71
  AbuseReports,
70
72
  Provenance,
@@ -166,9 +168,11 @@ __all__ = [
166
168
  "verify_resolve_webhook",
167
169
  "settings_conflict_current",
168
170
  "share_token_from",
171
+ "vault_resolve_digest",
172
+ "vault_resolve_message",
169
173
  "vault_unlock_message",
170
174
  ]
171
- __version__ = "0.69.0"
175
+ __version__ = "0.71.0"
172
176
  class _CallableGroup:
173
177
  """A resource group that is also callable — keeps ``client.chat(...)``,
174
178
  ``client.embeddings(...)``, ``client.models()`` and ``client.feedback(...)``
@@ -139,6 +139,7 @@ OPERATION_METHODS: Dict[str, str] = {
139
139
  "vaultUse": "vault.use",
140
140
  "vaultHttp": "vault.http_call",
141
141
  "vaultResolve": "vault.resolve",
142
+ "createVaultResolveChallenge": "vault.resolve_challenge",
142
143
  "createStudioAbuseReport": "abuse_reports.create",
143
144
  "checkProvenance": "provenance.check",
144
145
  "createStudioBatch": "batches.create",
@@ -2518,14 +2518,20 @@ class MediaOpJob(TypedDict):
2518
2518
  updated_at: str
2519
2519
 
2520
2520
 
2521
+ # What the production builds: `video` (MP4), `image` (PNG), `deck` (PPTX), `doc` (DOCX), `sheet` (XLSX), `audio`…
2522
+ StudioProductionKind = Literal['video', 'image', 'deck', 'doc', 'sheet', 'audio', 'carousel', 'print']
2523
+
2524
+
2521
2525
  class _StudioProductionCreateRequired(TypedDict):
2522
2526
  brief: str
2523
2527
 
2524
2528
 
2525
2529
  class StudioProductionCreate(_StudioProductionCreateRequired, total=False):
2526
2530
  """Studio production request"""
2531
+ kind: StudioProductionKind
2527
2532
  language: Literal['en', 'ro']
2528
2533
  aspect_ratio: Literal['16:9', '9:16', '1:1']
2534
+ duration_s: int
2529
2535
  starter: str
2530
2536
  voice: str
2531
2537
  narration: bool
@@ -2539,8 +2545,24 @@ class StudioProductionQa(TypedDict, total=False):
2539
2545
  skipped: str
2540
2546
 
2541
2547
 
2542
- class StudioProduction(TypedDict):
2543
- """Studio production"""
2548
+ class StudioProductionOutput(TypedDict):
2549
+ """A built output of a non-video production: the regular Studio asset plus its `role`. `url` is a 1 h signed GET,…"""
2550
+ asset_id: str
2551
+ kind: AssetKind
2552
+ status: Literal['pending', 'ready', 'failed', 'deleted']
2553
+ model: str
2554
+ url: Optional[str]
2555
+ mime_type: str
2556
+ bytes: int
2557
+ duration_seconds: Optional[float]
2558
+ width: Optional[int]
2559
+ height: Optional[int]
2560
+ created_at: str
2561
+ expires_at: Optional[str]
2562
+ role: Literal['image', 'cover', 'document', 'pages', 'pdf', 'audio']
2563
+
2564
+
2565
+ class _StudioProductionRequired(TypedDict):
2544
2566
  id: str
2545
2567
  object: Literal['studio.production']
2546
2568
  status: Literal['running', 'succeeded', 'failed']
@@ -2562,6 +2584,14 @@ class StudioProduction(TypedDict):
2562
2584
  created_at: str
2563
2585
 
2564
2586
 
2587
+ class StudioProduction(_StudioProductionRequired, total=False):
2588
+ """Studio production"""
2589
+ duration_s: Optional[int]
2590
+ kind: StudioProductionKind
2591
+ output_asset_id: Optional[str]
2592
+ outputs: List[StudioProductionOutput]
2593
+
2594
+
2565
2595
  class _MusicRoomCreateRequired(TypedDict):
2566
2596
  brief: str
2567
2597
 
@@ -5745,7 +5775,7 @@ class AccountPatchResponse(TypedDict):
5745
5775
  training_opt_out: bool
5746
5776
  consents: List[AccountConsent]
5747
5777
  username_error: Optional[Literal['invalid', 'taken', 'change_limit']]
5748
- referral_result: Optional[Literal['window_closed', 'none', 'invalid_code', 'self', 'already_referred', 'flagged', 'granted', 'cap_reached']]
5778
+ referral_result: Optional[Literal['window_closed', 'none', 'invalid_code', 'self', 'already_referred', 'flagged', 'pending_payment', 'granted', 'cap_reached']]
5749
5779
 
5750
5780
 
5751
5781
  class BillingSubscription(TypedDict):
@@ -7090,11 +7120,11 @@ class VaultUseDecision(_VaultUseDecisionRequired, total=False):
7090
7120
 
7091
7121
  class VaultDeviceKey(TypedDict):
7092
7122
  id: str
7093
- kind: Literal['android_keystore', 'windows_hello']
7123
+ kind: Literal['android_keystore', 'windows_hello', 'cli_ed25519']
7094
7124
  name: str
7095
- surface: Literal['phone', 'wear', 'desktop']
7125
+ surface: Literal['phone', 'wear', 'desktop', 'cli']
7096
7126
  public_key: str
7097
- attestation: Literal['android-strongbox', 'android-tee', 'tpm', 'hello-unattested']
7127
+ attestation: Literal['android-strongbox', 'android-tee', 'tpm', 'hello-unattested', 'software']
7098
7128
  attestation_info: Dict[str, Any]
7099
7129
  created_at: str
7100
7130
  last_used_at: Optional[str]
@@ -7106,6 +7136,17 @@ class VaultDeviceChallenge(TypedDict):
7106
7136
  expires_at: str
7107
7137
 
7108
7138
 
7139
+ class VaultResolveChallenge(TypedDict):
7140
+ challenge: str
7141
+ expires_at: str
7142
+
7143
+
7144
+ class VaultResolveProof(TypedDict):
7145
+ device_id: str
7146
+ challenge: str
7147
+ signature: str
7148
+
7149
+
7109
7150
  class VaultDeviceEnrollAttestationOption1(TypedDict):
7110
7151
  android_chain: List[str]
7111
7152
 
@@ -7121,9 +7162,9 @@ class VaultDeviceEnrollAttestationOption2(TypedDict):
7121
7162
 
7122
7163
  class _VaultDeviceEnrollRequired(TypedDict):
7123
7164
  device_id: str
7124
- kind: Literal['android_keystore', 'windows_hello']
7165
+ kind: Literal['android_keystore', 'windows_hello', 'cli_ed25519']
7125
7166
  name: str
7126
- surface: Literal['phone', 'wear', 'desktop']
7167
+ surface: Literal['phone', 'wear', 'desktop', 'cli']
7127
7168
  public_key: str
7128
7169
  nonce: str
7129
7170
  vault_signature: str
@@ -9708,9 +9749,14 @@ class VaultHttpBody(_VaultHttpBodyRequired, total=False):
9708
9749
  VaultHttpResponse = Dict[str, Any]
9709
9750
 
9710
9751
 
9752
+ # POST /v1/vault/resolve/challenge — 200 response
9753
+ CreateVaultResolveChallengeResponse = VaultResolveChallenge
9754
+
9755
+
9711
9756
  class _VaultResolveBodyRequired(TypedDict):
9712
9757
  handles: List[str]
9713
9758
  action: str
9759
+ proof: VaultResolveProof
9714
9760
 
9715
9761
 
9716
9762
  class VaultResolveBody(_VaultResolveBodyRequired, total=False):
@@ -10006,6 +10052,7 @@ __all__ = [
10006
10052
  'CreateTriggerResponse',
10007
10053
  'CreateVaultBody',
10008
10054
  'CreateVaultDeviceChallengeResponse',
10055
+ 'CreateVaultResolveChallengeResponse',
10009
10056
  'CreateVaultResponse',
10010
10057
  'CreateVaultRuleBody',
10011
10058
  'CreateVaultRuleResponse',
@@ -10815,6 +10862,8 @@ __all__ = [
10815
10862
  'StudioPostCopyRequest',
10816
10863
  'StudioProduction',
10817
10864
  'StudioProductionCreate',
10865
+ 'StudioProductionKind',
10866
+ 'StudioProductionOutput',
10818
10867
  'StudioProductionQa',
10819
10868
  'StudioPublicDecisionRequest',
10820
10869
  'StudioPublicNameRequest',
@@ -11018,6 +11067,8 @@ __all__ = [
11018
11067
  'VaultItemLimits',
11019
11068
  'VaultLockResult',
11020
11069
  'VaultResolveBody',
11070
+ 'VaultResolveChallenge',
11071
+ 'VaultResolveProof',
11021
11072
  'VaultResolveResponse',
11022
11073
  'VaultUnlockChallenge',
11023
11074
  'VaultUseBody',
@@ -13,7 +13,7 @@ from .media_ops import MEDIA_JOB_TERMINAL_STATUSES, MediaOps, is_media_op_job
13
13
  from .lyrics import Lyrics
14
14
  from .exports import Exports
15
15
  from .research import RESEARCH_TERMINAL_STATUSES, Research
16
- from .vault import Vault, vault_unlock_message
16
+ from .vault import Vault, vault_resolve_digest, vault_resolve_message, vault_unlock_message
17
17
  from .batches import Batches
18
18
  from .shares import Shares, share_token_from
19
19
  from .voice_revocations import VoiceRevocations
@@ -141,6 +141,8 @@ __all__ = [
141
141
  "VoiceRevocations",
142
142
  "Voices",
143
143
  "Vault",
144
+ "vault_resolve_digest",
145
+ "vault_resolve_message",
144
146
  "vault_unlock_message",
145
147
  "AbuseReports",
146
148
  "Provenance",
@@ -1,10 +1,16 @@
1
- """``/v1/studio/productions`` — codai Studio brief → finished promo video (RC-20): one model turn storyboards the brief
2
- on a built-in video starter, every scene gets an image and a voice-over line, the result is saved as an editable
3
- creative and a draft render is queued; a vision review may fix text once.
1
+ """``/v1/studio/productions`` — codai Studio brief → finished output (RC-20), one of 8 kinds (``kind``, default
2
+ ``video``): ``video`` (MP4 render), ``image`` (PNG), ``deck`` (PPTX), ``doc`` (DOCX), ``sheet`` (XLSX), ``carousel``
3
+ (ZIP of PNG slides + cover PNG), ``print`` (A4/A5 PDF) and ``audio`` (MP3 jingle or song).
4
4
 
5
- ``create`` returns 202 ``{id, status: "running", phase}`` after the render is queued (about a minute), ``retrieve``
5
+ ``video``: one model turn storyboards the brief on a built-in video starter, every scene gets an image and a voice-over
6
+ line, the result is saved as an editable creative and a draft render is queued; a vision review may fix text once.
7
+ Other kinds: one plan turn, generated images or music, an editable creative and the output built by the Studio
8
+ builders; the response adds ``kind``, ``output_asset_id`` and ``outputs`` (Studio assets with a ``role``).
9
+ ``starter``, ``voice``, ``narration`` and ``aspect_ratio`` apply to ``video`` only.
10
+
11
+ ``create`` returns 202 ``{id, status: "running", phase}`` after the output is queued (about a minute), ``retrieve``
6
12
  advances and reads it, ``wait`` polls until ``succeeded`` / ``failed`` and ``run`` does ``create`` + ``wait``. The
7
- storyboard and review turns are token-metered; images, voice-over and renders are billed at their usual prices;
13
+ plan and review turns are token-metered; images, music, voice-over and renders are billed at their usual prices;
8
14
  ``budget_micro_usd`` (default 3,000,000 = $3) caps the whole production.
9
15
  """
10
16
 
@@ -45,12 +51,17 @@ class Productions(Resource):
45
51
  title: Optional[str] = None,
46
52
  idempotency_key: Optional[str] = None,
47
53
  ext: Optional[RequestExtensions] = None,
54
+ kind: Optional[str] = None,
55
+ duration_s: Optional[int] = None,
48
56
  ) -> Dict[str, Any]:
49
- """``POST /v1/studio/productions`` — storyboard + images + voice-over + creative + queued draft render."""
57
+ """``POST /v1/studio/productions`` — plan + images / music + creative + the output being built
58
+ (``kind``: video | image | deck | doc | sheet | audio | carousel | print, default video)."""
50
59
  body: Dict[str, Any] = {"brief": brief}
51
60
  for k, v in (
61
+ ("kind", kind),
52
62
  ("language", language),
53
63
  ("aspect_ratio", aspect_ratio),
64
+ ("duration_s", duration_s),
54
65
  ("starter", starter),
55
66
  ("voice", voice),
56
67
  ("narration", narration),
@@ -5,12 +5,14 @@ client-side (bundles, envelopes, sealed keys); this resource moves those blobs.
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import base64
9
+ import hashlib
8
10
  from typing import Any, Dict, List, Optional
9
11
 
10
12
  from .._http import HttpClient, RequestExtensions
11
13
  from ._base import Resource, enc, list_of
12
14
 
13
- __all__ = ["Vault", "VaultDevices", "vault_unlock_message"]
15
+ __all__ = ["Vault", "VaultDevices", "vault_resolve_digest", "vault_resolve_message", "vault_unlock_message"]
14
16
 
15
17
 
16
18
  def vault_unlock_message(challenge: str, device_id_or_passkey: str) -> str:
@@ -18,6 +20,17 @@ def vault_unlock_message(challenge: str, device_id_or_passkey: str) -> str:
18
20
  return f"codai-vault/unlock/v1|{challenge}|{device_id_or_passkey}"
19
21
 
20
22
 
23
+ def vault_resolve_digest(handles: List[str], action: str) -> str:
24
+ """``base64url_nopad(SHA-256(handles joined by "\\n" + "\\n\\n" + action))``, handles and action exactly as sent."""
25
+ raw = hashlib.sha256(("\n".join(handles) + "\n\n" + action).encode("utf-8")).digest()
26
+ return base64.urlsafe_b64encode(raw).rstrip(b"=").decode("ascii")
27
+
28
+
29
+ def vault_resolve_message(challenge: str, device_id: str, digest: str) -> str:
30
+ """The UTF-8 message a resolve proof covers: ``codai-vault/resolve/v1|<challenge>|<device_id>|<digest>``."""
31
+ return f"codai-vault/resolve/v1|{challenge}|{device_id}|{digest}"
32
+
33
+
21
34
  def _seg(base: str, item_id: str, suffix: str = "") -> str:
22
35
  return f"{base}/{enc(item_id)}{suffix}"
23
36
 
@@ -320,6 +333,15 @@ class Vault(Resource):
320
333
  """``POST /v1/vault/http`` — brokered HTTPS call (``handle``, ``url``, ...); HTTP 202 = approval required."""
321
334
  return self._json("/v1/vault/http", ext=ext, method="POST", body=_clean(body), no_retry=True) or {}
322
335
 
336
+ def resolve_challenge(self, ext: Optional[RequestExtensions] = None) -> Dict[str, Any]:
337
+ """``POST /v1/vault/resolve/challenge`` — codai local brokers only; single-use challenge, 120 seconds."""
338
+ return self._json("/v1/vault/resolve/challenge", ext=ext, method="POST", body={}, no_retry=True)
339
+
323
340
  def resolve(self, body: Dict[str, Any], ext: Optional[RequestExtensions] = None) -> Dict[str, Any]:
324
- """``POST /v1/vault/resolve`` — codai local brokers only; HTTP 202 = approval required."""
341
+ """``POST /v1/vault/resolve`` — codai local brokers only; HTTP 202 = approval required.
342
+
343
+ ``body["proof"]`` is required: ``{"device_id", "challenge", "signature"}`` where the device key signs
344
+ :func:`vault_resolve_message` over a fresh :meth:`resolve_challenge` (400 ``proof_required`` /
345
+ ``invalid_challenge``, 403 ``invalid_device_proof``).
346
+ """
325
347
  return self._json("/v1/vault/resolve", ext=ext, method="POST", body=_clean(body), no_retry=True) or {}
@@ -24,7 +24,7 @@ def _chat_response(content="PONG", tool_calls=None):
24
24
 
25
25
 
26
26
  def test_version():
27
- assert codai.__version__ == "0.69.0"
27
+ assert codai.__version__ == "0.71.0"
28
28
  def test_chat_completions_create(client, stub):
29
29
  stub.reply(
30
30
  200,
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import pytest
6
6
 
7
- from codai import CodaiError, vault_unlock_message
7
+ from codai import CodaiError, vault_resolve_digest, vault_resolve_message, vault_unlock_message
8
8
 
9
9
  ITEM = "0b7d1a2e-1111-4222-8333-444455556666"
10
10
  DEVICE = {
@@ -88,9 +88,21 @@ def test_vault_broker_calls_and_approval_flow(client, stub):
88
88
  assert r == {"approval_id": "a2"}
89
89
  assert (stub.last.path, stub.last.json) == ("/v1/vault/http", {"handle": "github", "url": "https://api.github.com/user"})
90
90
 
91
+ stub.reply(200, {"challenge": "ch123", "expires_at": "2026-10-24T00:02:00Z"})
92
+ ch = client.vault.resolve_challenge()
93
+ assert (stub.last.method, stub.last.path, stub.last.json) == ("POST", "/v1/vault/resolve/challenge", {})
94
+ proof = {"device_id": "cli-abcd1234", "challenge": ch["challenge"], "signature": "sig"}
91
95
  stub.reply(202, {"approvals": ["a3"]})
92
- assert client.vault.resolve({"handles": ["db"], "action": "run"}) == {"approvals": ["a3"]}
93
- assert stub.last.path == "/v1/vault/resolve"
96
+ assert client.vault.resolve({"handles": ["db"], "action": "run", "proof": proof}) == {"approvals": ["a3"]}
97
+ assert (stub.last.path, stub.last.json) == ("/v1/vault/resolve", {"handles": ["db"], "action": "run", "proof": proof})
98
+
99
+
100
+ def test_vault_resolve_proof_message_matches_contract_vector():
101
+ digest = vault_resolve_digest(["github.token", "aws.secret"], "curl -H x https://api.github.com")
102
+ assert digest == "D_gl7EO9sGs-oo2tDZhv6JDMLE3vQ2Vo9jwdFPZRKdQ"
103
+ assert vault_resolve_message("ch123", "cli-abcd1234", digest) == (
104
+ "codai-vault/resolve/v1|ch123|cli-abcd1234|D_gl7EO9sGs-oo2tDZhv6JDMLE3vQ2Vo9jwdFPZRKdQ"
105
+ )
94
106
 
95
107
 
96
108
  def test_vault_devices_enrol_list_rename_revoke(client, stub):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes